dskcode 0.1.40 → 0.1.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +651 -36
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -1160,23 +1160,207 @@ function formatMoney(yuan) {
|
|
|
1160
1160
|
|
|
1161
1161
|
// src/ui/ToolCallBlock.tsx
|
|
1162
1162
|
import { Box as Box3, Text as Text4 } from "ink";
|
|
1163
|
-
|
|
1164
|
-
|
|
1163
|
+
|
|
1164
|
+
// src/agent/tool-call-summary.ts
|
|
1165
|
+
var SENSITIVE_KEYS = /* @__PURE__ */ new Set([
|
|
1166
|
+
"password",
|
|
1167
|
+
"token",
|
|
1168
|
+
"apikey",
|
|
1169
|
+
"api_key",
|
|
1170
|
+
"secret",
|
|
1171
|
+
"authorization",
|
|
1172
|
+
"auth",
|
|
1173
|
+
"credential",
|
|
1174
|
+
"credentials",
|
|
1175
|
+
"private_key",
|
|
1176
|
+
"privatekey"
|
|
1177
|
+
]);
|
|
1178
|
+
function templateToolCall(toolName, args) {
|
|
1179
|
+
if (!isPlainObject(args)) {
|
|
1180
|
+
return `${toolName}()`;
|
|
1181
|
+
}
|
|
1182
|
+
switch (toolName) {
|
|
1183
|
+
case "read_file":
|
|
1184
|
+
return `${toolName}(path=${shortStr(args.path)})`;
|
|
1185
|
+
case "write_file":
|
|
1186
|
+
return `${toolName}(path=${shortStr(args.path)})`;
|
|
1187
|
+
case "edit_file":
|
|
1188
|
+
return `${toolName}(path=${shortStr(args.path)}, old_text=${shortStr(args.old_text, 30)})`;
|
|
1189
|
+
case "multi_edit": {
|
|
1190
|
+
const edits = Array.isArray(args.edits) ? args.edits.length : 0;
|
|
1191
|
+
return `${toolName}(path=${shortStr(args.path)}, edits=${edits})`;
|
|
1192
|
+
}
|
|
1193
|
+
case "delete_range": {
|
|
1194
|
+
return `${toolName}(path=${shortStr(args.path)})`;
|
|
1195
|
+
}
|
|
1196
|
+
case "bash":
|
|
1197
|
+
return `${toolName}(command=${shortStr(args.command, 60)})`;
|
|
1198
|
+
case "ls":
|
|
1199
|
+
return `${toolName}(path=${shortStr(args.path ?? ".")})`;
|
|
1200
|
+
case "glob":
|
|
1201
|
+
return `${toolName}(pattern=${shortStr(args.pattern)})`;
|
|
1202
|
+
case "grep":
|
|
1203
|
+
return `${toolName}(pattern=${shortStr(args.pattern)}, path=${shortStr(args.path ?? ".")})`;
|
|
1204
|
+
case "fetch":
|
|
1205
|
+
return `${toolName}(url=${shortStr(args.url, 60)})`;
|
|
1206
|
+
default:
|
|
1207
|
+
const keys = Object.keys(args);
|
|
1208
|
+
if (keys.length === 0) return `${toolName}()`;
|
|
1209
|
+
const preview = keys.slice(0, 4).map((k) => `${k}=\u2026`).join(", ");
|
|
1210
|
+
const more = keys.length > 4 ? `, +${keys.length - 4}` : "";
|
|
1211
|
+
return `${toolName}(${preview}${more})`;
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
function maskSensitive(args) {
|
|
1215
|
+
return maskRecursive(args, /* @__PURE__ */ new WeakSet());
|
|
1216
|
+
}
|
|
1217
|
+
function maskRecursive(value, seen) {
|
|
1218
|
+
if (value === null || value === void 0) return value;
|
|
1219
|
+
if (typeof value !== "object") return value;
|
|
1220
|
+
const obj = value;
|
|
1221
|
+
if (seen.has(obj)) return "****";
|
|
1222
|
+
seen.add(obj);
|
|
1223
|
+
if (Array.isArray(value)) {
|
|
1224
|
+
return value.map((v) => maskRecursive(v, seen));
|
|
1225
|
+
}
|
|
1226
|
+
const out = {};
|
|
1227
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
1228
|
+
if (SENSITIVE_KEYS.has(k.toLowerCase())) {
|
|
1229
|
+
out[k] = "****";
|
|
1230
|
+
} else {
|
|
1231
|
+
out[k] = maskRecursive(v, seen);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
return out;
|
|
1235
|
+
}
|
|
1236
|
+
function formatArgsForDisplay(args, opts = {}) {
|
|
1237
|
+
const valueMaxLen = opts.valueMaxLen ?? 80;
|
|
1238
|
+
const doMask = opts.maskSensitive !== false;
|
|
1239
|
+
const maxKeys = opts.maxKeys ?? 6;
|
|
1240
|
+
if (!isPlainObject(args)) {
|
|
1241
|
+
const s = formatValue(args, valueMaxLen);
|
|
1242
|
+
return s;
|
|
1243
|
+
}
|
|
1244
|
+
const masked = doMask ? maskSensitive(args) : args;
|
|
1245
|
+
const entries = Object.entries(masked);
|
|
1246
|
+
const shown = entries.slice(0, maxKeys);
|
|
1247
|
+
const lines = shown.map(([k, v]) => `${k}: ${formatValue(v, valueMaxLen)}`);
|
|
1248
|
+
if (entries.length > maxKeys) {
|
|
1249
|
+
lines.push(`+${entries.length - maxKeys} more`);
|
|
1250
|
+
}
|
|
1251
|
+
return lines.join("\n");
|
|
1252
|
+
}
|
|
1253
|
+
function formatValue(v, maxLen = 80) {
|
|
1254
|
+
if (v === null) return "null";
|
|
1255
|
+
if (v === void 0) return "undefined";
|
|
1256
|
+
if (typeof v === "string") {
|
|
1257
|
+
return v.length > maxLen ? v.slice(0, maxLen - 1) + "\u2026" : v;
|
|
1258
|
+
}
|
|
1259
|
+
if (typeof v === "number" || typeof v === "boolean") return String(v);
|
|
1260
|
+
if (Array.isArray(v)) {
|
|
1261
|
+
if (v.length <= 3) return JSON.stringify(v);
|
|
1262
|
+
return `[\u2026${v.length} items\u2026]`;
|
|
1263
|
+
}
|
|
1264
|
+
if (typeof v === "object") {
|
|
1265
|
+
const obj = v;
|
|
1266
|
+
const keys = Object.keys(obj);
|
|
1267
|
+
if (keys.length <= 3) return JSON.stringify(obj);
|
|
1268
|
+
return `{\u2026${keys.length} keys\u2026}`;
|
|
1269
|
+
}
|
|
1270
|
+
return JSON.stringify(v);
|
|
1271
|
+
}
|
|
1272
|
+
function shortStr(v, maxLen = 40) {
|
|
1273
|
+
if (v === void 0 || v === null) return String(v);
|
|
1274
|
+
if (typeof v !== "string") return formatValue(v, maxLen);
|
|
1275
|
+
if (v.length === 0) return "(\u7A7A)";
|
|
1276
|
+
if (v.length > maxLen) return v.slice(0, maxLen - 1) + "\u2026";
|
|
1277
|
+
return v;
|
|
1278
|
+
}
|
|
1279
|
+
function isPlainObject(v) {
|
|
1280
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
// src/agent/tool-call-parser.ts
|
|
1284
|
+
function parseToolCallArgs(raw) {
|
|
1285
|
+
if (raw === "" || raw === void 0 || raw === null) {
|
|
1286
|
+
return { ok: true, args: {}, fixed: "" };
|
|
1287
|
+
}
|
|
1165
1288
|
try {
|
|
1166
|
-
const
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1289
|
+
const args = JSON.parse(raw);
|
|
1290
|
+
return { ok: true, args, fixed: raw };
|
|
1291
|
+
} catch (firstErr) {
|
|
1292
|
+
const fixed = fixStreamedJson(raw);
|
|
1293
|
+
try {
|
|
1294
|
+
const args = JSON.parse(fixed);
|
|
1295
|
+
return { ok: true, args, fixed };
|
|
1296
|
+
} catch (secondErr) {
|
|
1297
|
+
const firstMsg = firstErr instanceof Error ? firstErr.message : String(firstErr);
|
|
1298
|
+
if (isLikelyPartialJson(firstMsg, raw)) {
|
|
1299
|
+
return {
|
|
1300
|
+
ok: false,
|
|
1301
|
+
reason: "PARTIAL",
|
|
1302
|
+
partial: raw,
|
|
1303
|
+
hint: "\u53C2\u6570 JSON \u5C1A\u672A\u6536\u5168\uFF0C\u6D41\u5F0F\u62FC\u63A5\u4E2D\uFF1B\u53EF\u80FD\u662F\u7F51\u7EDC/\u5FC3\u8DF3\u5BFC\u81F4\u534A\u622A"
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1306
|
+
return {
|
|
1307
|
+
ok: false,
|
|
1308
|
+
reason: "INVALID_JSON",
|
|
1309
|
+
raw,
|
|
1310
|
+
error: firstMsg
|
|
1311
|
+
};
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
function fixStreamedJson(partial) {
|
|
1316
|
+
let end = partial.length;
|
|
1317
|
+
let backslashes = 0;
|
|
1318
|
+
for (let i = partial.length - 1; i >= 0; i--) {
|
|
1319
|
+
if (partial[i] === "\\") backslashes++;
|
|
1320
|
+
else break;
|
|
1321
|
+
}
|
|
1322
|
+
if (backslashes % 2 === 1) end--;
|
|
1323
|
+
let json = partial.slice(0, end);
|
|
1324
|
+
const stack = [];
|
|
1325
|
+
let inString = false;
|
|
1326
|
+
let escaped = false;
|
|
1327
|
+
for (let i = 0; i < json.length; i++) {
|
|
1328
|
+
const ch = json[i];
|
|
1329
|
+
if (escaped) {
|
|
1330
|
+
escaped = false;
|
|
1331
|
+
continue;
|
|
1332
|
+
}
|
|
1333
|
+
if (ch === "\\") {
|
|
1334
|
+
escaped = true;
|
|
1335
|
+
continue;
|
|
1336
|
+
}
|
|
1337
|
+
if (ch === '"') {
|
|
1338
|
+
inString = !inString;
|
|
1339
|
+
continue;
|
|
1340
|
+
}
|
|
1341
|
+
if (inString) continue;
|
|
1342
|
+
if (ch === "{") stack.push("}");
|
|
1343
|
+
else if (ch === "[") stack.push("]");
|
|
1344
|
+
else if (ch === "}" || ch === "]") stack.pop();
|
|
1176
1345
|
}
|
|
1346
|
+
if (inString) json += '"';
|
|
1347
|
+
while (stack.length > 0) json += stack.pop();
|
|
1348
|
+
return json;
|
|
1349
|
+
}
|
|
1350
|
+
function isLikelyPartialJson(errMsg, raw) {
|
|
1351
|
+
if (errMsg.includes("Unexpected end of JSON")) return true;
|
|
1352
|
+
if (errMsg.includes("Unterminated string")) return true;
|
|
1353
|
+
if (raw.length < 5) return true;
|
|
1354
|
+
return false;
|
|
1177
1355
|
}
|
|
1178
|
-
|
|
1179
|
-
|
|
1356
|
+
|
|
1357
|
+
// src/ui/ToolCallBlock.tsx
|
|
1358
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1359
|
+
function ToolCallBlock({ call, parsedArgs, schemaIssues }) {
|
|
1360
|
+
const resolvedArgs = parsedArgs ?? resolveArgsFromRaw(call.arguments);
|
|
1361
|
+
const safeArgs = maskSensitive(resolvedArgs);
|
|
1362
|
+
const summary = templateToolCall(call.name, safeArgs);
|
|
1363
|
+
const argsDisplay = formatArgsForDisplay(safeArgs);
|
|
1180
1364
|
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginLeft: 3, marginTop: 1, children: [
|
|
1181
1365
|
/* @__PURE__ */ jsxs3(Box3, { children: [
|
|
1182
1366
|
/* @__PURE__ */ jsxs3(Text4, { dimColor: true, children: [
|
|
@@ -1188,9 +1372,34 @@ function ToolCallBlock({ call }) {
|
|
|
1188
1372
|
"\u2500".repeat(Math.max(1, 30 - call.name.length))
|
|
1189
1373
|
] })
|
|
1190
1374
|
] }),
|
|
1191
|
-
/* @__PURE__ */
|
|
1375
|
+
/* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginLeft: 2, children: [
|
|
1376
|
+
/* @__PURE__ */ jsx3(Text4, { dimColor: true, children: summary }),
|
|
1377
|
+
/* @__PURE__ */ jsx3(Text4, { dimColor: true, children: argsDisplay })
|
|
1378
|
+
] }),
|
|
1379
|
+
schemaIssues && schemaIssues.length > 0 && /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginLeft: 2, children: [
|
|
1380
|
+
/* @__PURE__ */ jsxs3(Text4, { color: "yellow", children: [
|
|
1381
|
+
"\u26A0 schema \u95EE\u9898\uFF08",
|
|
1382
|
+
schemaIssues.length,
|
|
1383
|
+
"\uFF09"
|
|
1384
|
+
] }),
|
|
1385
|
+
schemaIssues.slice(0, 3).map((issue, i) => /* @__PURE__ */ jsxs3(Text4, { color: "yellow", children: [
|
|
1386
|
+
" - ",
|
|
1387
|
+
issue.message
|
|
1388
|
+
] }, i)),
|
|
1389
|
+
schemaIssues.length > 3 && /* @__PURE__ */ jsxs3(Text4, { color: "yellow", children: [
|
|
1390
|
+
" ...\u8FD8\u6709 ",
|
|
1391
|
+
schemaIssues.length - 3,
|
|
1392
|
+
" \u6761"
|
|
1393
|
+
] })
|
|
1394
|
+
] })
|
|
1192
1395
|
] });
|
|
1193
1396
|
}
|
|
1397
|
+
function resolveArgsFromRaw(raw) {
|
|
1398
|
+
if (!raw) return {};
|
|
1399
|
+
const parsed = parseToolCallArgs(raw);
|
|
1400
|
+
if (parsed.ok) return parsed.args;
|
|
1401
|
+
return {};
|
|
1402
|
+
}
|
|
1194
1403
|
|
|
1195
1404
|
// src/ui/HighlightedText.tsx
|
|
1196
1405
|
import { Box as Box4, Text as Text5 } from "ink";
|
|
@@ -2188,7 +2397,7 @@ function CompactionProgress({ state, contentWidth }) {
|
|
|
2188
2397
|
// package.json
|
|
2189
2398
|
var package_default = {
|
|
2190
2399
|
name: "dskcode",
|
|
2191
|
-
version: "0.1.
|
|
2400
|
+
version: "0.1.41",
|
|
2192
2401
|
repository: {
|
|
2193
2402
|
type: "git",
|
|
2194
2403
|
url: "git+https://github.com/Awu12277/deepseek-agent-cli/tree/main"
|
|
@@ -2224,9 +2433,8 @@ var package_default = {
|
|
|
2224
2433
|
"lint:fix": "oxlint --fix src/ tests/",
|
|
2225
2434
|
format: "prettier --write src/ tests/",
|
|
2226
2435
|
"format:check": "prettier --check src/ tests/",
|
|
2227
|
-
test: "vitest run",
|
|
2228
|
-
"test:
|
|
2229
|
-
"test:coverage": "vitest run --coverage",
|
|
2436
|
+
test: "vitest run --exclude 'tests/checkpoint.test.ts' --exclude 'tests/session-rewind.test.ts'",
|
|
2437
|
+
"test:checkpoint": "vitest run tests/checkpoint.test.ts tests/session-rewind.test.ts",
|
|
2230
2438
|
prepublishOnly: "npm run build && npm run test"
|
|
2231
2439
|
},
|
|
2232
2440
|
dependencies: {
|
|
@@ -2237,7 +2445,8 @@ var package_default = {
|
|
|
2237
2445
|
ink: "^7.1.0",
|
|
2238
2446
|
"ink-spinner": "^5.0.0",
|
|
2239
2447
|
"ink-text-input": "^6.0.0",
|
|
2240
|
-
react: "^19.2.7"
|
|
2448
|
+
react: "^19.2.7",
|
|
2449
|
+
zod: "^3.25.76"
|
|
2241
2450
|
},
|
|
2242
2451
|
devDependencies: {
|
|
2243
2452
|
"@types/node": "^22.14.0",
|
|
@@ -2554,12 +2763,19 @@ function fallbackSummary(turns) {
|
|
|
2554
2763
|
const preview = userMsg.content.slice(0, FALLBACK_USER_PREVIEW);
|
|
2555
2764
|
lines.push(`\u56DE\u5408${i + 1} \u7528\u6237: ${preview}`);
|
|
2556
2765
|
}
|
|
2557
|
-
const
|
|
2766
|
+
const toolCalls = [];
|
|
2558
2767
|
for (const m of turn) {
|
|
2559
|
-
if (m.toolCalls) for (const tc of m.toolCalls)
|
|
2768
|
+
if (m.toolCalls) for (const tc of m.toolCalls) toolCalls.push({ name: tc.name, argsRaw: tc.arguments });
|
|
2560
2769
|
}
|
|
2561
|
-
if (
|
|
2562
|
-
|
|
2770
|
+
if (toolCalls.length > 0) {
|
|
2771
|
+
const summaries = toolCalls.map((tc) => {
|
|
2772
|
+
const parsed = parseToolCallArgs(tc.argsRaw);
|
|
2773
|
+
if (!parsed.ok) {
|
|
2774
|
+
return `${tc.name}(\u89E3\u6790\u5931\u8D25)`;
|
|
2775
|
+
}
|
|
2776
|
+
return templateToolCall(tc.name, maskSensitive(parsed.args));
|
|
2777
|
+
});
|
|
2778
|
+
lines.push(` \u8C03\u7528\u5DE5\u5177: ${summaries.join("; ")}`);
|
|
2563
2779
|
}
|
|
2564
2780
|
}
|
|
2565
2781
|
return lines.join("\n");
|
|
@@ -2870,6 +3086,326 @@ var ToolRegistry = class {
|
|
|
2870
3086
|
}
|
|
2871
3087
|
};
|
|
2872
3088
|
|
|
3089
|
+
// src/tool/zod-schema-validator.ts
|
|
3090
|
+
import { z } from "zod";
|
|
3091
|
+
function zodSafeValidate(args, schema) {
|
|
3092
|
+
const result = schema.safeParse(args);
|
|
3093
|
+
if (result.success) {
|
|
3094
|
+
return { ok: true, issues: [] };
|
|
3095
|
+
}
|
|
3096
|
+
const issues = result.error.issues.map(
|
|
3097
|
+
(zi) => zodIssueToValidationIssue(zi, args)
|
|
3098
|
+
);
|
|
3099
|
+
return { ok: issues.length === 0, issues };
|
|
3100
|
+
}
|
|
3101
|
+
function zodIssueToValidationIssue(zi, rootArgs) {
|
|
3102
|
+
const path = formatPath(zi.path);
|
|
3103
|
+
const expected = formatExpected(zi);
|
|
3104
|
+
const received = describeReceived(rootArgs, zi.path);
|
|
3105
|
+
const message = formatMessage(path, expected, received, zi);
|
|
3106
|
+
return { path, expected, received, message };
|
|
3107
|
+
}
|
|
3108
|
+
function formatPath(path) {
|
|
3109
|
+
if (path.length === 0) return "$";
|
|
3110
|
+
let out = "$";
|
|
3111
|
+
for (const seg of path) {
|
|
3112
|
+
if (typeof seg === "number") {
|
|
3113
|
+
out += `[${seg}]`;
|
|
3114
|
+
} else if (typeof seg === "string") {
|
|
3115
|
+
out += `.${seg}`;
|
|
3116
|
+
} else {
|
|
3117
|
+
out += "[]";
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
return out;
|
|
3121
|
+
}
|
|
3122
|
+
function formatExpected(zi) {
|
|
3123
|
+
switch (zi.code) {
|
|
3124
|
+
case "invalid_type": {
|
|
3125
|
+
const exp = zi.expected;
|
|
3126
|
+
return exp;
|
|
3127
|
+
}
|
|
3128
|
+
case "too_small": {
|
|
3129
|
+
const t = zi.type;
|
|
3130
|
+
const min = zi.minimum;
|
|
3131
|
+
if (t === "string") return `length >= ${min}`;
|
|
3132
|
+
if (t === "array") return `length >= ${min}`;
|
|
3133
|
+
if (t === "number") return `>= ${min}`;
|
|
3134
|
+
return `>= ${min}`;
|
|
3135
|
+
}
|
|
3136
|
+
case "too_big": {
|
|
3137
|
+
const t = zi.type;
|
|
3138
|
+
const max = zi.maximum;
|
|
3139
|
+
if (t === "string") return `length <= ${max}`;
|
|
3140
|
+
if (t === "array") return `length <= ${max}`;
|
|
3141
|
+
if (t === "number") return `<= ${max}`;
|
|
3142
|
+
return `<= ${max}`;
|
|
3143
|
+
}
|
|
3144
|
+
case "invalid_enum_value": {
|
|
3145
|
+
const opts = zi.options;
|
|
3146
|
+
return `enum[${opts.map((o) => JSON.stringify(o)).join(",")}]`;
|
|
3147
|
+
}
|
|
3148
|
+
case "invalid_string": {
|
|
3149
|
+
const v = zi.validation;
|
|
3150
|
+
return `string ${v}`;
|
|
3151
|
+
}
|
|
3152
|
+
case "unrecognized_keys": {
|
|
3153
|
+
const keys = zi.keys;
|
|
3154
|
+
return `\u672A\u5728 schema \u4E2D\u5B9A\u4E49: ${keys.join(",")}`;
|
|
3155
|
+
}
|
|
3156
|
+
case "invalid_literal": {
|
|
3157
|
+
const exp = zi.expected;
|
|
3158
|
+
return `literal[${JSON.stringify(exp)}]`;
|
|
3159
|
+
}
|
|
3160
|
+
default:
|
|
3161
|
+
return zi.code;
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
3164
|
+
function describeReceived(rootArgs, path) {
|
|
3165
|
+
let cur = rootArgs;
|
|
3166
|
+
for (const seg of path) {
|
|
3167
|
+
if (cur === null || cur === void 0) return "undefined";
|
|
3168
|
+
if (typeof cur !== "object") return "undefined";
|
|
3169
|
+
if (typeof seg === "number") {
|
|
3170
|
+
if (!Array.isArray(cur)) return "undefined";
|
|
3171
|
+
cur = cur[seg];
|
|
3172
|
+
} else if (typeof seg === "string") {
|
|
3173
|
+
cur = cur[seg];
|
|
3174
|
+
} else {
|
|
3175
|
+
return "undefined";
|
|
3176
|
+
}
|
|
3177
|
+
}
|
|
3178
|
+
if (cur === null) return "null";
|
|
3179
|
+
if (cur === void 0) return "undefined";
|
|
3180
|
+
if (typeof cur === "string") {
|
|
3181
|
+
return cur.length > 60 ? JSON.stringify(cur.slice(0, 57) + "...") : JSON.stringify(cur);
|
|
3182
|
+
}
|
|
3183
|
+
if (typeof cur === "number" || typeof cur === "boolean") return JSON.stringify(cur);
|
|
3184
|
+
if (Array.isArray(cur)) return `array(len=${cur.length})`;
|
|
3185
|
+
if (typeof cur === "object") {
|
|
3186
|
+
const keys = Object.keys(cur);
|
|
3187
|
+
return `object{keys=${keys.length}}`;
|
|
3188
|
+
}
|
|
3189
|
+
return typeof cur;
|
|
3190
|
+
}
|
|
3191
|
+
function formatMessage(path, expected, received, zi) {
|
|
3192
|
+
if (zi.code === "invalid_type") {
|
|
3193
|
+
return `${path} \u5E94\u4E3A ${expected}\uFF0C\u5B9E\u9645\u4E3A ${received}`;
|
|
3194
|
+
}
|
|
3195
|
+
if (zi.code === "too_small") {
|
|
3196
|
+
const t = zi.type;
|
|
3197
|
+
const min = zi.minimum;
|
|
3198
|
+
if (t === "string") return `${path} \u957F\u5EA6\u5E94 >= ${min}\uFF0C\u5B9E\u9645\u4E3A ${received}`;
|
|
3199
|
+
if (t === "array") return `${path} \u957F\u5EA6\u5E94 >= ${min}\uFF0C\u5B9E\u9645\u4E3A ${received}`;
|
|
3200
|
+
if (t === "number") return `${path} \u5E94 >= ${min}\uFF0C\u5B9E\u9645\u4E3A ${received}`;
|
|
3201
|
+
return `${path} \u5E94 >= ${min}\uFF0C\u5B9E\u9645\u4E3A ${received}`;
|
|
3202
|
+
}
|
|
3203
|
+
if (zi.code === "too_big") {
|
|
3204
|
+
const t = zi.type;
|
|
3205
|
+
const max = zi.maximum;
|
|
3206
|
+
if (t === "string") return `${path} \u957F\u5EA6\u5E94 <= ${max}\uFF0C\u5B9E\u9645\u4E3A ${received}`;
|
|
3207
|
+
if (t === "array") return `${path} \u957F\u5EA6\u5E94 <= ${max}\uFF0C\u5B9E\u9645\u4E3A ${received}`;
|
|
3208
|
+
if (t === "number") return `${path} \u5E94 <= ${max}\uFF0C\u5B9E\u9645\u4E3A ${received}`;
|
|
3209
|
+
return `${path} \u5E94 <= ${max}\uFF0C\u5B9E\u9645\u4E3A ${received}`;
|
|
3210
|
+
}
|
|
3211
|
+
if (zi.code === "invalid_enum_value") {
|
|
3212
|
+
return `${path} \u5E94\u4E3A enum \u503C\u4E4B\u4E00\uFF0C\u5B9E\u9645\u4E3A ${received}`;
|
|
3213
|
+
}
|
|
3214
|
+
if (zi.code === "unrecognized_keys") {
|
|
3215
|
+
const keys = zi.keys;
|
|
3216
|
+
return `${path} \u4E0D\u5141\u8BB8\u952E: ${keys.join(", ")}`;
|
|
3217
|
+
}
|
|
3218
|
+
if (zi.code === "invalid_literal") {
|
|
3219
|
+
return `${path} \u5E94\u4E3A ${expected}\uFF0C\u5B9E\u9645\u4E3A ${received}`;
|
|
3220
|
+
}
|
|
3221
|
+
if (zi.code === "invalid_string") {
|
|
3222
|
+
return `${path} \u4E0D\u5339\u914D ${expected}`;
|
|
3223
|
+
}
|
|
3224
|
+
return `${path} ${zi.message}\uFF08\u671F\u671B ${expected}\uFF0C\u5B9E\u9645 ${received}\uFF09`;
|
|
3225
|
+
}
|
|
3226
|
+
|
|
3227
|
+
// src/tool/schema-validator.ts
|
|
3228
|
+
function validateArgs(args, schema) {
|
|
3229
|
+
if (isZodSchema(schema)) {
|
|
3230
|
+
return zodSafeValidate(args, schema);
|
|
3231
|
+
}
|
|
3232
|
+
const issues = [];
|
|
3233
|
+
if (!isPlainObject2(schema)) {
|
|
3234
|
+
return { ok: true, issues };
|
|
3235
|
+
}
|
|
3236
|
+
if (schema.type !== void 0 && schema.type !== "object") {
|
|
3237
|
+
return { ok: true, issues };
|
|
3238
|
+
}
|
|
3239
|
+
validateObject(args, schema, "$", issues);
|
|
3240
|
+
return { ok: issues.length === 0, issues };
|
|
3241
|
+
}
|
|
3242
|
+
function isZodSchema(schema) {
|
|
3243
|
+
if (schema === null || typeof schema !== "object") return false;
|
|
3244
|
+
const obj = schema;
|
|
3245
|
+
return "_def" in obj && typeof obj.parse === "function" && typeof obj.safeParse === "function";
|
|
3246
|
+
}
|
|
3247
|
+
function validateObject(value, schema, path, issues) {
|
|
3248
|
+
if (!isPlainObject2(value)) {
|
|
3249
|
+
issues.push({
|
|
3250
|
+
path,
|
|
3251
|
+
expected: "object",
|
|
3252
|
+
received: describe(value),
|
|
3253
|
+
message: `${path} \u5E94\u4E3A object\uFF0C\u5B9E\u9645\u4E3A ${describe(value)}`
|
|
3254
|
+
});
|
|
3255
|
+
return;
|
|
3256
|
+
}
|
|
3257
|
+
for (const key of schema.required ?? []) {
|
|
3258
|
+
if (!(key in value)) {
|
|
3259
|
+
issues.push({
|
|
3260
|
+
path: `${path}.${key}`,
|
|
3261
|
+
expected: "present",
|
|
3262
|
+
received: "missing",
|
|
3263
|
+
message: `${path}.${key} \u662F\u5FC5\u586B\u5B57\u6BB5`
|
|
3264
|
+
});
|
|
3265
|
+
}
|
|
3266
|
+
}
|
|
3267
|
+
const props = schema.properties ?? {};
|
|
3268
|
+
const additional = schema.additionalProperties;
|
|
3269
|
+
for (const [k, v] of Object.entries(value)) {
|
|
3270
|
+
const propSchema = props[k];
|
|
3271
|
+
if (propSchema === void 0) {
|
|
3272
|
+
if (additional === false) {
|
|
3273
|
+
issues.push({
|
|
3274
|
+
path: `${path}.${k}`,
|
|
3275
|
+
expected: "\u672A\u5728 schema \u4E2D\u5B9A\u4E49",
|
|
3276
|
+
received: describe(v),
|
|
3277
|
+
message: `${path}.${k} \u4E0D\u5728\u5141\u8BB8\u5B57\u6BB5\u4E2D`
|
|
3278
|
+
});
|
|
3279
|
+
}
|
|
3280
|
+
continue;
|
|
3281
|
+
}
|
|
3282
|
+
validateProperty(v, propSchema, `${path}.${k}`, issues);
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
function validateProperty(value, schema, path, issues) {
|
|
3286
|
+
if (schema.type !== void 0) {
|
|
3287
|
+
const actual = jsonTypeOf(value);
|
|
3288
|
+
if (!typeMatches(actual, schema.type)) {
|
|
3289
|
+
issues.push({
|
|
3290
|
+
path,
|
|
3291
|
+
expected: schema.type,
|
|
3292
|
+
received: describe(value),
|
|
3293
|
+
message: `${path} \u5E94\u4E3A ${schema.type}\uFF0C\u5B9E\u9645\u4E3A ${actual}`
|
|
3294
|
+
});
|
|
3295
|
+
return;
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
if (schema.enum && schema.enum.length > 0) {
|
|
3299
|
+
if (!isScalar(value)) {
|
|
3300
|
+
issues.push({
|
|
3301
|
+
path,
|
|
3302
|
+
expected: `enum[${schema.enum.map((e) => JSON.stringify(e)).join(",")}]`,
|
|
3303
|
+
received: describe(value),
|
|
3304
|
+
message: `${path} \u5E94\u4E3A enum \u503C\u4E4B\u4E00\uFF0C\u5B9E\u9645\u4E3A ${describe(value)}`
|
|
3305
|
+
});
|
|
3306
|
+
} else if (!schema.enum.includes(value)) {
|
|
3307
|
+
issues.push({
|
|
3308
|
+
path,
|
|
3309
|
+
expected: `enum[${schema.enum.map((e) => JSON.stringify(e)).join(",")}]`,
|
|
3310
|
+
received: describe(value),
|
|
3311
|
+
message: `${path} \u5E94\u4E3A enum \u503C\u4E4B\u4E00\uFF0C\u5B9E\u9645\u4E3A ${JSON.stringify(value)}`
|
|
3312
|
+
});
|
|
3313
|
+
}
|
|
3314
|
+
}
|
|
3315
|
+
if (typeof value === "number") {
|
|
3316
|
+
if (schema.minimum !== void 0 && value < schema.minimum) {
|
|
3317
|
+
issues.push({
|
|
3318
|
+
path,
|
|
3319
|
+
expected: `>= ${schema.minimum}`,
|
|
3320
|
+
received: String(value),
|
|
3321
|
+
message: `${path} \u5E94 >= ${schema.minimum}\uFF0C\u5B9E\u9645\u4E3A ${value}`
|
|
3322
|
+
});
|
|
3323
|
+
}
|
|
3324
|
+
if (schema.maximum !== void 0 && value > schema.maximum) {
|
|
3325
|
+
issues.push({
|
|
3326
|
+
path,
|
|
3327
|
+
expected: `<= ${schema.maximum}`,
|
|
3328
|
+
received: String(value),
|
|
3329
|
+
message: `${path} \u5E94 <= ${schema.maximum}\uFF0C\u5B9E\u9645\u4E3A ${value}`
|
|
3330
|
+
});
|
|
3331
|
+
}
|
|
3332
|
+
}
|
|
3333
|
+
if (typeof value === "string") {
|
|
3334
|
+
if (schema.minLength !== void 0 && value.length < schema.minLength) {
|
|
3335
|
+
issues.push({
|
|
3336
|
+
path,
|
|
3337
|
+
expected: `length >= ${schema.minLength}`,
|
|
3338
|
+
received: `length ${value.length}`,
|
|
3339
|
+
message: `${path} \u957F\u5EA6\u5E94 >= ${schema.minLength}\uFF0C\u5B9E\u9645\u4E3A ${value.length}`
|
|
3340
|
+
});
|
|
3341
|
+
}
|
|
3342
|
+
if (schema.maxLength !== void 0 && value.length > schema.maxLength) {
|
|
3343
|
+
issues.push({
|
|
3344
|
+
path,
|
|
3345
|
+
expected: `length <= ${schema.maxLength}`,
|
|
3346
|
+
received: `length ${value.length}`,
|
|
3347
|
+
message: `${path} \u957F\u5EA6\u5E94 <= ${schema.maxLength}\uFF0C\u5B9E\u9645\u4E3A ${value.length}`
|
|
3348
|
+
});
|
|
3349
|
+
}
|
|
3350
|
+
if (schema.pattern !== void 0) {
|
|
3351
|
+
try {
|
|
3352
|
+
const re = new RegExp(schema.pattern);
|
|
3353
|
+
if (!re.test(value)) {
|
|
3354
|
+
issues.push({
|
|
3355
|
+
path,
|
|
3356
|
+
expected: `pattern ${schema.pattern}`,
|
|
3357
|
+
received: describe(value),
|
|
3358
|
+
message: `${path} \u4E0D\u5339\u914D pattern ${schema.pattern}`
|
|
3359
|
+
});
|
|
3360
|
+
}
|
|
3361
|
+
} catch {
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
if (schema.properties !== void 0) {
|
|
3366
|
+
validateObject(value, schema, path, issues);
|
|
3367
|
+
}
|
|
3368
|
+
if (schema.items !== void 0 && Array.isArray(value)) {
|
|
3369
|
+
for (let i = 0; i < value.length; i++) {
|
|
3370
|
+
validateProperty(value[i], schema.items, `${path}[${i}]`, issues);
|
|
3371
|
+
}
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3374
|
+
function isPlainObject2(v) {
|
|
3375
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
3376
|
+
}
|
|
3377
|
+
function isScalar(v) {
|
|
3378
|
+
return v === null || typeof v === "string" || typeof v === "number" || typeof v === "boolean";
|
|
3379
|
+
}
|
|
3380
|
+
function jsonTypeOf(v) {
|
|
3381
|
+
if (v === null) return "null";
|
|
3382
|
+
if (Array.isArray(v)) return "array";
|
|
3383
|
+
if (typeof v === "object") return "object";
|
|
3384
|
+
if (typeof v === "number") return Number.isInteger(v) ? "integer" : "number";
|
|
3385
|
+
return typeof v;
|
|
3386
|
+
}
|
|
3387
|
+
function typeMatches(actual, expected) {
|
|
3388
|
+
if (actual === expected) return true;
|
|
3389
|
+
if (expected === "integer" && actual === "number") return true;
|
|
3390
|
+
if (expected === "number" && actual === "integer") return true;
|
|
3391
|
+
return false;
|
|
3392
|
+
}
|
|
3393
|
+
function describe(v) {
|
|
3394
|
+
if (v === null) return "null";
|
|
3395
|
+
if (v === void 0) return "undefined";
|
|
3396
|
+
if (typeof v === "string") {
|
|
3397
|
+
const s = v.length > 60 ? v.slice(0, 57) + "..." : v;
|
|
3398
|
+
return JSON.stringify(s);
|
|
3399
|
+
}
|
|
3400
|
+
if (typeof v === "number" || typeof v === "boolean") return JSON.stringify(v);
|
|
3401
|
+
if (Array.isArray(v)) return `array(len=${v.length})`;
|
|
3402
|
+
if (typeof v === "object") {
|
|
3403
|
+
const keys = Object.keys(v);
|
|
3404
|
+
return `object{keys=${keys.length}}`;
|
|
3405
|
+
}
|
|
3406
|
+
return typeof v;
|
|
3407
|
+
}
|
|
3408
|
+
|
|
2873
3409
|
// src/agent/tool-executor.ts
|
|
2874
3410
|
var ToolExecutor = class {
|
|
2875
3411
|
/** 工具注册表(只读引用) */
|
|
@@ -2996,11 +3532,38 @@ var ToolExecutor = class {
|
|
|
2996
3532
|
record: { name: toolName, success: false, error: "TOOL_NOT_FOUND", timestamp }
|
|
2997
3533
|
};
|
|
2998
3534
|
}
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3535
|
+
const parsed = parseToolCallArgs(tc.arguments ?? "");
|
|
3536
|
+
if (!parsed.ok) {
|
|
3537
|
+
if (parsed.reason === "PARTIAL") {
|
|
3538
|
+
const errMsg2 = `\u5DE5\u5177 "${toolName}" \u53C2\u6570\u5C1A\u672A\u6536\u5168\uFF0C\u672C\u8F6E\u8DF3\u8FC7\uFF08\u6D41\u5F0F\u5206\u7247\u5BFC\u81F4\uFF09\uFF1A${parsed.hint}`;
|
|
3539
|
+
return {
|
|
3540
|
+
item: {
|
|
3541
|
+
name: toolName,
|
|
3542
|
+
callId: tc.id,
|
|
3543
|
+
result: { success: false, data: errMsg2, error: "PARTIAL_JSON" }
|
|
3544
|
+
},
|
|
3545
|
+
record: { name: toolName, success: false, error: "PARTIAL_JSON", timestamp }
|
|
3546
|
+
};
|
|
3547
|
+
}
|
|
3548
|
+
const errMsg = `\u5DE5\u5177 "${toolName}" \u53C2\u6570\u4E0D\u662F\u5408\u6CD5 JSON\uFF1A${parsed.error}
|
|
3549
|
+
\u539F\u6587: ${parsed.raw.slice(0, 200)}`;
|
|
3550
|
+
return {
|
|
3551
|
+
item: {
|
|
3552
|
+
name: toolName,
|
|
3553
|
+
callId: tc.id,
|
|
3554
|
+
result: { success: false, data: errMsg, error: "INVALID_JSON" }
|
|
3555
|
+
},
|
|
3556
|
+
record: { name: toolName, success: false, error: "INVALID_JSON", timestamp }
|
|
3557
|
+
};
|
|
3558
|
+
}
|
|
3559
|
+
const toolArgs = parsed.args;
|
|
3560
|
+
let schemaIssues;
|
|
3561
|
+
const schemaToCheck = tool.schema ?? tool.parameters;
|
|
3562
|
+
if (schemaToCheck) {
|
|
3563
|
+
const validation = validateArgs(toolArgs, schemaToCheck);
|
|
3564
|
+
if (!validation.ok) {
|
|
3565
|
+
schemaIssues = validation.issues;
|
|
3566
|
+
}
|
|
3004
3567
|
}
|
|
3005
3568
|
const gateResult = await this.#gate.check(toolName, toolArgs);
|
|
3006
3569
|
if (!gateResult) {
|
|
@@ -3020,10 +3583,29 @@ var ToolExecutor = class {
|
|
|
3020
3583
|
}
|
|
3021
3584
|
}
|
|
3022
3585
|
try {
|
|
3023
|
-
|
|
3586
|
+
let result = await this.#invokeTool(tool, toolArgs);
|
|
3587
|
+
if (schemaIssues && schemaIssues.length > 0) {
|
|
3588
|
+
const issuesText = schemaIssues.map((i) => ` - ${i.message}`).join("\n");
|
|
3589
|
+
const tag = result.success ? "[schema \u8B66\u544A]" : "[schema \u95EE\u9898]";
|
|
3590
|
+
result = {
|
|
3591
|
+
...result,
|
|
3592
|
+
data: `${result.data}
|
|
3593
|
+
|
|
3594
|
+
${tag} \u53C2\u6570\u4E0D\u7B26\u5408 schema\uFF1A
|
|
3595
|
+
${issuesText}`,
|
|
3596
|
+
issues: schemaIssues
|
|
3597
|
+
};
|
|
3598
|
+
}
|
|
3599
|
+
const record = {
|
|
3600
|
+
name: toolName,
|
|
3601
|
+
success: result.success,
|
|
3602
|
+
error: result.error,
|
|
3603
|
+
timestamp,
|
|
3604
|
+
...schemaIssues ? { issues: schemaIssues } : {}
|
|
3605
|
+
};
|
|
3024
3606
|
return {
|
|
3025
3607
|
item: { name: toolName, callId: tc.id, result },
|
|
3026
|
-
record
|
|
3608
|
+
record
|
|
3027
3609
|
};
|
|
3028
3610
|
} catch (err) {
|
|
3029
3611
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -3122,7 +3704,7 @@ var Reflector = class {
|
|
|
3122
3704
|
const reflections = [];
|
|
3123
3705
|
for (const item of items) {
|
|
3124
3706
|
if (item.result.success) continue;
|
|
3125
|
-
const r = this.#ruleRepeatedFailure(item) ?? this.#ruleFileNotFound(item) ?? this.#ruleTextNotFound(item) ?? this.#rulePermissionDenied(item) ?? this.#ruleOutOfWriteRoot(item, ctx.writeRoots);
|
|
3707
|
+
const r = this.#ruleRepeatedFailure(item) ?? this.#ruleFileNotFound(item) ?? this.#ruleTextNotFound(item) ?? this.#rulePermissionDenied(item) ?? this.#ruleOutOfWriteRoot(item, ctx.writeRoots) ?? this.#ruleInvalidArgs(item);
|
|
3126
3708
|
if (r) reflections.push(r);
|
|
3127
3709
|
if (reflections.length >= this.#maxReflections) break;
|
|
3128
3710
|
}
|
|
@@ -3264,6 +3846,25 @@ var Reflector = class {
|
|
|
3264
3846
|
if (text.length <= this.#maxHintChars) return text;
|
|
3265
3847
|
return text.slice(0, this.#maxHintChars - 3) + "...";
|
|
3266
3848
|
}
|
|
3849
|
+
/**
|
|
3850
|
+
* R5 INVALID_ARGS:参数不符合工具的 JSON Schema。
|
|
3851
|
+
*
|
|
3852
|
+
* 阶段 2 新增:tool-executor 校验后会把 issues 放进 result.issues,
|
|
3853
|
+
* 这里读取并生成反思。注意是 warn-only,不阻塞工具继续执行。
|
|
3854
|
+
*
|
|
3855
|
+
* @pure 仅读入参
|
|
3856
|
+
*/
|
|
3857
|
+
#ruleInvalidArgs(item) {
|
|
3858
|
+
const issues = item.result.issues;
|
|
3859
|
+
if (!issues || issues.length === 0) return null;
|
|
3860
|
+
const shown = issues.slice(0, 3).map((i) => i.message).join("; ");
|
|
3861
|
+
const more = issues.length > 3 ? `\uFF08\u53E6\u6709 ${issues.length - 3} \u6761\uFF09` : "";
|
|
3862
|
+
return {
|
|
3863
|
+
category: "invalid_args",
|
|
3864
|
+
toolName: item.name,
|
|
3865
|
+
hint: `\`${item.name}\` \u53C2\u6570\u4E0D\u7B26\u5408 schema\uFF1A${shown}${more}\u3002\u8BF7\u68C0\u67E5\u5FC5\u586B\u5B57\u6BB5\u3001\u7C7B\u578B\u4E0E enum \u767D\u540D\u5355\u3002`
|
|
3866
|
+
};
|
|
3867
|
+
}
|
|
3267
3868
|
};
|
|
3268
3869
|
|
|
3269
3870
|
// src/agent/tool-definitions.ts
|
|
@@ -3779,7 +4380,12 @@ async function restoreToClean(cwd) {
|
|
|
3779
4380
|
async function discardCheckpoint(checkpoint) {
|
|
3780
4381
|
if (!checkpoint.isGitRepo || !checkpoint.stashSha) return;
|
|
3781
4382
|
const { cwd, stashSha } = checkpoint;
|
|
3782
|
-
|
|
4383
|
+
let shas;
|
|
4384
|
+
try {
|
|
4385
|
+
shas = await listStashShas(cwd);
|
|
4386
|
+
} catch {
|
|
4387
|
+
return;
|
|
4388
|
+
}
|
|
3783
4389
|
const idx = shas.indexOf(stashSha);
|
|
3784
4390
|
if (idx < 0) return;
|
|
3785
4391
|
try {
|
|
@@ -3976,11 +4582,19 @@ var ConversationLogger = class _ConversationLogger {
|
|
|
3976
4582
|
this.log({ ts: Date.now(), type: "reasoning", content, round });
|
|
3977
4583
|
}
|
|
3978
4584
|
/** 记录工具调用 */
|
|
3979
|
-
logToolCall(name, callId, args, round) {
|
|
3980
|
-
this.log({
|
|
4585
|
+
logToolCall(name, callId, args, round, parsedArgs) {
|
|
4586
|
+
this.log({
|
|
4587
|
+
ts: Date.now(),
|
|
4588
|
+
type: "tool_call",
|
|
4589
|
+
name,
|
|
4590
|
+
callId,
|
|
4591
|
+
arguments: args,
|
|
4592
|
+
...parsedArgs !== void 0 ? { parsedArgs } : {},
|
|
4593
|
+
round
|
|
4594
|
+
});
|
|
3981
4595
|
}
|
|
3982
4596
|
/** 记录工具结果 */
|
|
3983
|
-
logToolResult(name, callId, success, data, error, elapsed, round) {
|
|
4597
|
+
logToolResult(name, callId, success, data, error, elapsed, round, schemaIssues) {
|
|
3984
4598
|
this.log({
|
|
3985
4599
|
ts: Date.now(),
|
|
3986
4600
|
type: "tool_result",
|
|
@@ -3990,6 +4604,7 @@ var ConversationLogger = class _ConversationLogger {
|
|
|
3990
4604
|
data: truncate(data),
|
|
3991
4605
|
...error ? { error } : {},
|
|
3992
4606
|
...elapsed !== void 0 ? { elapsed } : {},
|
|
4607
|
+
...schemaIssues ? { schemaIssues } : {},
|
|
3993
4608
|
round
|
|
3994
4609
|
});
|
|
3995
4610
|
}
|