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 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
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1164
- function formatArgsSummary(args) {
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 parsed = JSON.parse(args);
1167
- const lines = Object.entries(parsed).map(([key, value]) => {
1168
- const val = String(value);
1169
- const truncated = val.length > 80 ? val.slice(0, 77) + "..." : val;
1170
- return ` ${key}: ${truncated}`;
1171
- });
1172
- return lines.join("\n");
1173
- } catch {
1174
- const truncated = args.length > 120 ? args.slice(0, 117) + "..." : args;
1175
- return ` ${truncated}`;
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
- function ToolCallBlock({ call }) {
1179
- const argsDisplay = formatArgsSummary(call.arguments);
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__ */ jsx3(Box3, { flexDirection: "column", children: /* @__PURE__ */ jsx3(Text4, { dimColor: true, children: argsDisplay }) })
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.40",
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:watch": "vitest",
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 toolNames = /* @__PURE__ */ new Set();
2766
+ const toolCalls = [];
2558
2767
  for (const m of turn) {
2559
- if (m.toolCalls) for (const tc of m.toolCalls) toolNames.add(tc.name);
2768
+ if (m.toolCalls) for (const tc of m.toolCalls) toolCalls.push({ name: tc.name, argsRaw: tc.arguments });
2560
2769
  }
2561
- if (toolNames.size > 0) {
2562
- lines.push(` \u8C03\u7528\u5DE5\u5177: ${[...toolNames].join(", ")}`);
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
- let toolArgs;
3000
- try {
3001
- toolArgs = tc.arguments ? JSON.parse(tc.arguments) : {};
3002
- } catch {
3003
- toolArgs = {};
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
- const result = await this.#invokeTool(tool, toolArgs);
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: { name: toolName, success: result.success, error: result.error, timestamp }
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
- const shas = await listStashShas(cwd);
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({ ts: Date.now(), type: "tool_call", name, callId, arguments: args, round });
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
  }