@epoch-agent/core 0.16.0 → 0.18.0
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.d.ts +7439 -7426
- package/dist/index.js +316 -245
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1013,6 +1013,11 @@ function createAskQuestionTool(deps) {
|
|
|
1013
1013
|
}
|
|
1014
1014
|
};
|
|
1015
1015
|
}
|
|
1016
|
+
// src/agent/types.ts
|
|
1017
|
+
function isAbortSignal(signal) {
|
|
1018
|
+
return !!signal && typeof signal.addEventListener === "function" && typeof signal.removeEventListener === "function";
|
|
1019
|
+
}
|
|
1020
|
+
// src/tools/builtin/code-exec.ts
|
|
1016
1021
|
var MAX_CODE_LENGTH = 1e5;
|
|
1017
1022
|
function createCodeExecTool(sandbox) {
|
|
1018
1023
|
const iso = sandbox.getIsolation();
|
|
@@ -1058,7 +1063,7 @@ function createCodeExecTool(sandbox) {
|
|
|
1058
1063
|
},
|
|
1059
1064
|
operation: "code_exec",
|
|
1060
1065
|
describeTarget: (args) => `${String(args["language"] ?? "")}:${String(args["code"] ?? "")}`,
|
|
1061
|
-
async execute(args,
|
|
1066
|
+
async execute(args, ctx) {
|
|
1062
1067
|
const start = Date.now();
|
|
1063
1068
|
const language = String(args["language"] ?? "");
|
|
1064
1069
|
const code = String(args["code"] ?? "");
|
|
@@ -1096,7 +1101,8 @@ function createCodeExecTool(sandbox) {
|
|
|
1096
1101
|
language,
|
|
1097
1102
|
code,
|
|
1098
1103
|
timeout,
|
|
1099
|
-
allowNetwork: args["allow_network"] === true
|
|
1104
|
+
allowNetwork: args["allow_network"] === true,
|
|
1105
|
+
...isAbortSignal(ctx.signal) ? { signal: ctx.signal } : {}
|
|
1100
1106
|
});
|
|
1101
1107
|
const header = `[\u9694\u79BB: ${result.isolation === "os" ? "OS \u7EA7" : "\u4EC5\u8FDB\u7A0B\u7EA7"}]`;
|
|
1102
1108
|
const body2 = result.output || "(\u65E0\u8F93\u51FA)";
|
|
@@ -1108,7 +1114,9 @@ ${body2}`,
|
|
|
1108
1114
|
error: {
|
|
1109
1115
|
code: 6004,
|
|
1110
1116
|
message: result.error ?? "\u6267\u884C\u5931\u8D25",
|
|
1111
|
-
...result.errorType === "oom" ? { suggestion: "\u51CF\u5C11\u5185\u5B58\u5360\u7528\uFF0C\u6216\u5206\u6279\u5904\u7406\u6570\u636E" } :
|
|
1117
|
+
...result.errorType === "oom" ? { suggestion: "\u51CF\u5C11\u5185\u5B58\u5360\u7528\uFF0C\u6216\u5206\u6279\u5904\u7406\u6570\u636E" } : (
|
|
1118
|
+
result.errorType === "timeout" && !ctx.signal?.aborted ? { suggestion: "\u4F18\u5316\u7B97\u6CD5\uFF0C\u6216\u63D0\u9AD8 timeout\uFF08\u4E0A\u9650 120 \u79D2\uFF09" } : {}
|
|
1119
|
+
)
|
|
1112
1120
|
},
|
|
1113
1121
|
duration: result.duration
|
|
1114
1122
|
};
|
|
@@ -1118,6 +1126,218 @@ ${body2}`, duration: result.duration };
|
|
|
1118
1126
|
}
|
|
1119
1127
|
};
|
|
1120
1128
|
}
|
|
1129
|
+
var WINDOW_CHARS = 1024;
|
|
1130
|
+
var NGRAM = 16;
|
|
1131
|
+
var MAX_DISTINCT_RATIO = 0.2;
|
|
1132
|
+
var CHECK_EVERY_CHARS = 256;
|
|
1133
|
+
var SUSTAIN_CHECKS = 2;
|
|
1134
|
+
var MIN_NORMALIZED_CHARS = 256;
|
|
1135
|
+
function normalizeRuns(text) {
|
|
1136
|
+
return text.replace(/\s+/g, " ").replace(/(.)\1{3,}/gu, (_m, c) => c.repeat(3));
|
|
1137
|
+
}
|
|
1138
|
+
function distinctNgramRatio(text, n = NGRAM) {
|
|
1139
|
+
if (text.length < n) return 1;
|
|
1140
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1141
|
+
let total = 0;
|
|
1142
|
+
for (let i = 0; i + n <= text.length; i++) {
|
|
1143
|
+
seen.add(text.slice(i, i + n));
|
|
1144
|
+
total++;
|
|
1145
|
+
}
|
|
1146
|
+
return total === 0 ? 1 : seen.size / total;
|
|
1147
|
+
}
|
|
1148
|
+
var RepetitionDetector = class {
|
|
1149
|
+
buf = "";
|
|
1150
|
+
sinceCheck = 0;
|
|
1151
|
+
streak = 0;
|
|
1152
|
+
chars = 0;
|
|
1153
|
+
hit = null;
|
|
1154
|
+
push(delta) {
|
|
1155
|
+
if (this.hit) return true;
|
|
1156
|
+
if (!delta) return false;
|
|
1157
|
+
this.chars += delta.length;
|
|
1158
|
+
this.buf = (this.buf + delta).slice(-WINDOW_CHARS);
|
|
1159
|
+
this.sinceCheck += delta.length;
|
|
1160
|
+
if (this.sinceCheck < CHECK_EVERY_CHARS) return false;
|
|
1161
|
+
this.sinceCheck = 0;
|
|
1162
|
+
if (this.buf.length < WINDOW_CHARS) return false;
|
|
1163
|
+
const norm = normalizeRuns(this.buf);
|
|
1164
|
+
if (norm.length < MIN_NORMALIZED_CHARS) {
|
|
1165
|
+
this.streak = 0;
|
|
1166
|
+
return false;
|
|
1167
|
+
}
|
|
1168
|
+
const ratio = distinctNgramRatio(norm);
|
|
1169
|
+
if (ratio > MAX_DISTINCT_RATIO) {
|
|
1170
|
+
this.streak = 0;
|
|
1171
|
+
return false;
|
|
1172
|
+
}
|
|
1173
|
+
if (++this.streak < SUSTAIN_CHECKS) return false;
|
|
1174
|
+
this.hit = { chars: this.chars, distinctRatio: ratio };
|
|
1175
|
+
return true;
|
|
1176
|
+
}
|
|
1177
|
+
pushAll(text) {
|
|
1178
|
+
for (let i = 0; i < text.length; i += CHECK_EVERY_CHARS) {
|
|
1179
|
+
if (this.push(text.slice(i, i + CHECK_EVERY_CHARS))) return true;
|
|
1180
|
+
}
|
|
1181
|
+
return false;
|
|
1182
|
+
}
|
|
1183
|
+
isLooping() {
|
|
1184
|
+
return this.hit !== null;
|
|
1185
|
+
}
|
|
1186
|
+
getInfo() {
|
|
1187
|
+
if (!this.hit) return null;
|
|
1188
|
+
return t("agent_run.looping_info", {
|
|
1189
|
+
chars: this.hit.chars,
|
|
1190
|
+
percent: Math.round(this.hit.distinctRatio * 100)
|
|
1191
|
+
});
|
|
1192
|
+
}
|
|
1193
|
+
};
|
|
1194
|
+
// src/agent/turn.ts
|
|
1195
|
+
async function* runTurn(opts) {
|
|
1196
|
+
if (opts.streaming) {
|
|
1197
|
+
return yield* runTurnStreaming(opts);
|
|
1198
|
+
}
|
|
1199
|
+
return yield* runTurnBuffered(opts);
|
|
1200
|
+
}
|
|
1201
|
+
async function* runTurnStreaming(opts) {
|
|
1202
|
+
const { provider, input, guard, reasoningGuard } = opts;
|
|
1203
|
+
const repetition = new RepetitionDetector();
|
|
1204
|
+
const reasoningRepetition = new RepetitionDetector();
|
|
1205
|
+
let text = "";
|
|
1206
|
+
let reasoningText = "";
|
|
1207
|
+
let finishReason = "stop";
|
|
1208
|
+
let looping;
|
|
1209
|
+
let usage;
|
|
1210
|
+
const toolCalls = [];
|
|
1211
|
+
let streamError;
|
|
1212
|
+
let model;
|
|
1213
|
+
let requestedModel;
|
|
1214
|
+
let warnings;
|
|
1215
|
+
const ac = new AbortController();
|
|
1216
|
+
const unlink = linkAbort(input.signal, ac);
|
|
1217
|
+
try {
|
|
1218
|
+
for await (const chunk of provider.stream({ ...input, signal: ac.signal })) {
|
|
1219
|
+
switch (chunk.type) {
|
|
1220
|
+
case "text-delta": {
|
|
1221
|
+
const raw = chunk.textDelta ?? "";
|
|
1222
|
+
if (!raw) break;
|
|
1223
|
+
const delta = guard ? guard.push(raw) : raw;
|
|
1224
|
+
if (!delta) break;
|
|
1225
|
+
text += delta;
|
|
1226
|
+
yield { type: "text-delta", text: delta };
|
|
1227
|
+
if (repetition.push(delta)) looping = repetition;
|
|
1228
|
+
break;
|
|
1229
|
+
}
|
|
1230
|
+
case "reasoning-delta": {
|
|
1231
|
+
const raw = chunk.textDelta ?? "";
|
|
1232
|
+
if (!raw) break;
|
|
1233
|
+
const delta = reasoningGuard ? reasoningGuard.push(raw) : raw;
|
|
1234
|
+
if (!delta) break;
|
|
1235
|
+
reasoningText += delta;
|
|
1236
|
+
yield { type: "reasoning-delta", text: delta };
|
|
1237
|
+
if (reasoningRepetition.push(delta)) looping = reasoningRepetition;
|
|
1238
|
+
break;
|
|
1239
|
+
}
|
|
1240
|
+
case "tool-call":
|
|
1241
|
+
if (chunk.toolCall) {
|
|
1242
|
+
toolCalls.push({
|
|
1243
|
+
toolCallId: chunk.toolCall.toolCallId,
|
|
1244
|
+
toolName: chunk.toolCall.toolName,
|
|
1245
|
+
input: chunk.toolCall.input
|
|
1246
|
+
});
|
|
1247
|
+
}
|
|
1248
|
+
break;
|
|
1249
|
+
case "finish":
|
|
1250
|
+
if (chunk.usage) usage = chunk.usage;
|
|
1251
|
+
if (chunk.finishReason) finishReason = chunk.finishReason;
|
|
1252
|
+
if (chunk.model) model = chunk.model;
|
|
1253
|
+
if (chunk.requestedModel) requestedModel = chunk.requestedModel;
|
|
1254
|
+
if (chunk.warnings) warnings = chunk.warnings;
|
|
1255
|
+
break;
|
|
1256
|
+
case "error":
|
|
1257
|
+
streamError = chunk.error ?? t("agent_run.unknown_provider_error");
|
|
1258
|
+
break;
|
|
1259
|
+
case "tool-result":
|
|
1260
|
+
break;
|
|
1261
|
+
}
|
|
1262
|
+
if (looping) {
|
|
1263
|
+
ac.abort();
|
|
1264
|
+
break;
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
} finally {
|
|
1268
|
+
unlink();
|
|
1269
|
+
}
|
|
1270
|
+
if (streamError && !looping) throw new Error(streamError);
|
|
1271
|
+
if (guard) {
|
|
1272
|
+
const tail = guard.flush();
|
|
1273
|
+
if (tail) {
|
|
1274
|
+
text += tail;
|
|
1275
|
+
yield { type: "text-delta", text: tail };
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
if (reasoningGuard) {
|
|
1279
|
+
const tail = reasoningGuard.flush();
|
|
1280
|
+
if (tail) {
|
|
1281
|
+
reasoningText += tail;
|
|
1282
|
+
yield { type: "reasoning-delta", text: tail };
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
return {
|
|
1286
|
+
text,
|
|
1287
|
+
...usage ? { usage } : {},
|
|
1288
|
+
finishReason: looping ? "looping" : finishReason,
|
|
1289
|
+
...looping ? { loopingInfo: looping.getInfo() ?? "" } : {},
|
|
1290
|
+
...toolCalls.length > 0 ? { toolCalls } : {},
|
|
1291
|
+
...reasoningText ? { reasoningText } : {},
|
|
1292
|
+
...model ? { model } : {},
|
|
1293
|
+
...requestedModel ? { requestedModel } : {},
|
|
1294
|
+
...warnings && warnings.length > 0 ? { warnings } : {}
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
1297
|
+
async function* runTurnBuffered(opts) {
|
|
1298
|
+
const { provider, input, guard, reasoningGuard } = opts;
|
|
1299
|
+
const result = await provider.generate(input);
|
|
1300
|
+
const reasoningText = result.reasoningText ? filterWhole(reasoningGuard, result.reasoningText) : result.reasoningText;
|
|
1301
|
+
if (reasoningText) {
|
|
1302
|
+
yield { type: "reasoning-delta", text: reasoningText };
|
|
1303
|
+
}
|
|
1304
|
+
const text = filterWhole(guard, result.text);
|
|
1305
|
+
if (text) {
|
|
1306
|
+
yield { type: "text-delta", text };
|
|
1307
|
+
}
|
|
1308
|
+
const { reasoningText: _replaced, ...rest } = result;
|
|
1309
|
+
const textLoop = new RepetitionDetector();
|
|
1310
|
+
const reasoningLoop = new RepetitionDetector();
|
|
1311
|
+
textLoop.pushAll(text);
|
|
1312
|
+
if (reasoningText) reasoningLoop.pushAll(reasoningText);
|
|
1313
|
+
let looping;
|
|
1314
|
+
if (textLoop.isLooping()) looping = textLoop;
|
|
1315
|
+
else if (reasoningLoop.isLooping()) looping = reasoningLoop;
|
|
1316
|
+
return {
|
|
1317
|
+
...rest,
|
|
1318
|
+
text,
|
|
1319
|
+
...reasoningText ? { reasoningText } : {},
|
|
1320
|
+
...looping ? { finishReason: "looping", loopingInfo: looping.getInfo() ?? "" } : {}
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
function linkAbort(outer, ac) {
|
|
1324
|
+
if (!outer) return () => {
|
|
1325
|
+
};
|
|
1326
|
+
if (outer.aborted) {
|
|
1327
|
+
ac.abort(outer.reason);
|
|
1328
|
+
return () => {
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
const onAbort = () => ac.abort(outer.reason);
|
|
1332
|
+
outer.addEventListener("abort", onAbort, { once: true });
|
|
1333
|
+
return () => outer.removeEventListener("abort", onAbort);
|
|
1334
|
+
}
|
|
1335
|
+
function filterWhole(guard, text) {
|
|
1336
|
+
if (!guard || !text) return text;
|
|
1337
|
+
return guard.push(text) + guard.flush();
|
|
1338
|
+
}
|
|
1339
|
+
// src/delegate/manager.ts
|
|
1340
|
+
var ABORTED = "\u5DF2\u4E2D\u6B62\uFF1A\u7528\u6237\u7EC8\u6B62\u4E86\u8FD9\u4E00\u8F6E";
|
|
1121
1341
|
var BLOCKED_TOOLS = Object.freeze([
|
|
1122
1342
|
"delegate_task",
|
|
1123
1343
|
"clarify",
|
|
@@ -1147,7 +1367,7 @@ var DelegateManager = class {
|
|
|
1147
1367
|
...config
|
|
1148
1368
|
};
|
|
1149
1369
|
}
|
|
1150
|
-
async delegate(args, parentSessionId) {
|
|
1370
|
+
async delegate(args, parentSessionId, signal) {
|
|
1151
1371
|
const start = Date.now();
|
|
1152
1372
|
const tasks = this.normalizeTasks(args);
|
|
1153
1373
|
const maxConcurrent = args.max_concurrent ?? this.config.max_concurrent_children;
|
|
@@ -1158,7 +1378,7 @@ var DelegateManager = class {
|
|
|
1158
1378
|
duration: 0
|
|
1159
1379
|
};
|
|
1160
1380
|
}
|
|
1161
|
-
const results = await this.runWithConcurrency(tasks, maxConcurrent, parentSessionId);
|
|
1381
|
+
const results = await this.runWithConcurrency(tasks, maxConcurrent, parentSessionId, signal);
|
|
1162
1382
|
const summary = this.buildSummary(results, Date.now() - start);
|
|
1163
1383
|
return {
|
|
1164
1384
|
success: results.every((r) => r.success),
|
|
@@ -1173,12 +1393,12 @@ var DelegateManager = class {
|
|
|
1173
1393
|
if (args.goal) return [{ goal: args.goal, ...args.role ? { role: args.role } : {} }];
|
|
1174
1394
|
return [];
|
|
1175
1395
|
}
|
|
1176
|
-
async runWithConcurrency(tasks, maxConcurrent, parentSessionId) {
|
|
1396
|
+
async runWithConcurrency(tasks, maxConcurrent, parentSessionId, signal) {
|
|
1177
1397
|
const results = [];
|
|
1178
1398
|
for (let i = 0; i < tasks.length; i += maxConcurrent) {
|
|
1179
1399
|
const batch = tasks.slice(i, i + maxConcurrent);
|
|
1180
1400
|
const batchResults = await Promise.allSettled(
|
|
1181
|
-
batch.map((t81) => this.runSingleTask(t81, parentSessionId))
|
|
1401
|
+
batch.map((t81) => this.runSingleTask(t81, parentSessionId, signal))
|
|
1182
1402
|
);
|
|
1183
1403
|
for (const r of batchResults) {
|
|
1184
1404
|
if (r.status === "fulfilled") {
|
|
@@ -1196,14 +1416,40 @@ var DelegateManager = class {
|
|
|
1196
1416
|
}
|
|
1197
1417
|
return results;
|
|
1198
1418
|
}
|
|
1199
|
-
async runSingleTask(task, parentSessionId) {
|
|
1419
|
+
async runSingleTask(task, parentSessionId, signal) {
|
|
1200
1420
|
const start = Date.now();
|
|
1201
1421
|
const sessionId = `delegate-${randomUUID().slice(0, 8)}`;
|
|
1422
|
+
const failed = (error, summary = "") => ({
|
|
1423
|
+
goal: task.goal,
|
|
1424
|
+
success: false,
|
|
1425
|
+
summary,
|
|
1426
|
+
error,
|
|
1427
|
+
duration: Date.now() - start,
|
|
1428
|
+
...task.role ? { role: task.role } : {}
|
|
1429
|
+
});
|
|
1430
|
+
if (signal?.aborted) return failed(ABORTED);
|
|
1431
|
+
const controller = new AbortController();
|
|
1432
|
+
const unlink = linkAbort(isAbortSignal(signal) ? signal : void 0, controller);
|
|
1433
|
+
const ms = this.config.task_timeout;
|
|
1434
|
+
let timer;
|
|
1202
1435
|
try {
|
|
1203
|
-
const result = await
|
|
1204
|
-
this.runner(
|
|
1205
|
-
|
|
1206
|
-
|
|
1436
|
+
const result = await Promise.race([
|
|
1437
|
+
this.runner(
|
|
1438
|
+
task.goal,
|
|
1439
|
+
task.context ?? "",
|
|
1440
|
+
sessionId,
|
|
1441
|
+
task.role,
|
|
1442
|
+
parentSessionId,
|
|
1443
|
+
controller.signal
|
|
1444
|
+
),
|
|
1445
|
+
new Promise((_, reject2) => {
|
|
1446
|
+
timer = setTimeout(() => {
|
|
1447
|
+
reject2(new Error(`\u8D85\u65F6 (${ms / 1e3}s)`));
|
|
1448
|
+
controller.abort();
|
|
1449
|
+
}, ms);
|
|
1450
|
+
})
|
|
1451
|
+
]);
|
|
1452
|
+
if (controller.signal.aborted) return failed(ABORTED, result.text);
|
|
1207
1453
|
return {
|
|
1208
1454
|
goal: task.goal,
|
|
1209
1455
|
success: !result.error,
|
|
@@ -1214,22 +1460,13 @@ var DelegateManager = class {
|
|
|
1214
1460
|
};
|
|
1215
1461
|
} catch (err) {
|
|
1216
1462
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1217
|
-
return
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
duration: Date.now() - start,
|
|
1223
|
-
...task.role ? { role: task.role } : {}
|
|
1224
|
-
};
|
|
1463
|
+
if (signal?.aborted) return failed(ABORTED);
|
|
1464
|
+
return failed(msg.includes("\u8D85\u65F6") ? `[\u8D85\u65F6] ${msg}` : msg);
|
|
1465
|
+
} finally {
|
|
1466
|
+
clearTimeout(timer);
|
|
1467
|
+
unlink();
|
|
1225
1468
|
}
|
|
1226
1469
|
}
|
|
1227
|
-
async runWithTimeout(promise, ms) {
|
|
1228
|
-
const timeout = new Promise(
|
|
1229
|
-
(_, reject2) => setTimeout(() => reject2(new Error(`\u8D85\u65F6 (${ms / 1e3}s)`)), ms)
|
|
1230
|
-
);
|
|
1231
|
-
return Promise.race([promise, timeout]);
|
|
1232
|
-
}
|
|
1233
1470
|
buildSummary(results, totalDuration) {
|
|
1234
1471
|
const lines = [];
|
|
1235
1472
|
const succeeded = results.filter((r) => r.success).length;
|
|
@@ -1373,12 +1610,13 @@ function createDelegateTool(runner, config, roles) {
|
|
|
1373
1610
|
...topRole ? { role: topRole } : {},
|
|
1374
1611
|
...maxConcurrent ? { max_concurrent: maxConcurrent } : {}
|
|
1375
1612
|
},
|
|
1376
|
-
ctx.sessionId
|
|
1613
|
+
ctx.sessionId,
|
|
1614
|
+
ctx.signal
|
|
1377
1615
|
);
|
|
1378
1616
|
return {
|
|
1379
1617
|
success: result.success,
|
|
1380
1618
|
output: result.output,
|
|
1381
|
-
...result.success ? {} : { error: { code: 7002, message:
|
|
1619
|
+
...result.success ? {} : { error: { code: 7002, message: result.output } },
|
|
1382
1620
|
duration: result.duration
|
|
1383
1621
|
};
|
|
1384
1622
|
}
|
|
@@ -10442,6 +10680,17 @@ function blankResponseNote(chars) {
|
|
|
10442
10680
|
"\u4E0A\u4E00\u8F6E\u7684\u5DE5\u5177\u7ED3\u679C\u90FD\u8FD8\u5728\u4E0A\u9762\u7684\u4E0A\u4E0B\u6587\u91CC\uFF0C\u76F4\u63A5\u636E\u6B64\u4F5C\u7B54\uFF0C\u522B\u628A\u90A3\u4E9B\u5DE5\u5177\u91CD\u8DD1\u4E00\u904D\u3002"
|
|
10443
10681
|
].join("\n");
|
|
10444
10682
|
}
|
|
10683
|
+
var STALL_NOTE_ARGS_MAX_CHARS = 200;
|
|
10684
|
+
function stallNote(detail) {
|
|
10685
|
+
const args = detail.args.length > STALL_NOTE_ARGS_MAX_CHARS ? `${detail.args.slice(0, STALL_NOTE_ARGS_MAX_CHARS)}\u2026` : detail.args;
|
|
10686
|
+
const fact = detail.kind === "failure" ? `\u4F60\u5DF2\u7ECF\u7528\u540C\u4E00\u4EFD\u53C2\u6570\u8C03\u4E86 ${detail.count} \u6B21 ${detail.tool}\uFF08${args}\uFF09\uFF0C\u6BCF\u6B21\u90FD\u5931\u8D25\uFF0C\u800C\u4E14\u662F\u540C\u4E00\u6761\u9519\u8BEF\uFF1A${detail.error ?? ""}` : `\u4F60\u5DF2\u7ECF\u8FDE\u7EED ${detail.count} \u8F6E\u7528\u540C\u4E00\u4EFD\u53C2\u6570\u8C03 ${detail.tool}\uFF08${args}\uFF09\uFF0C\u7ED3\u679C\u4E0D\u4F1A\u56E0\u4E3A\u518D\u8C03\u4E00\u6B21\u800C\u6539\u53D8\u3002`;
|
|
10687
|
+
return [
|
|
10688
|
+
`${fact} \u2014\u2014 \u8FD9\u662F\u5728\u539F\u5730\u6253\u8F6C\u3002`,
|
|
10689
|
+
detail.kind === "failure" ? "\u522B\u518D\u539F\u6837\u91CD\u8BD5\u3002\u5148\u8BFB\u61C2\u8FD9\u6761\u9519\u8BEF\u8BF4\u7684\u662F\u4EC0\u4E48\uFF0C\u7136\u540E\u6362\u4E00\u79CD\u505A\u6CD5\uFF1A\u6362\u53C2\u6570\u3001\u6362\u4E00\u4E2A\u5DE5\u5177\uFF0C\u6216\u8005\u7ED5\u5F00\u8FD9\u4E00\u6B65\u7528\u5DF2\u7ECF\u62FF\u5230\u7684\u7ED3\u679C\u5F80\u4E0B\u8D70\u3002" : "\u522B\u518D\u539F\u6837\u8C03\u4E00\u6B21\u3002\u5B83\u7684\u7ED3\u679C\u5DF2\u7ECF\u5728\u4E0A\u9762\u7684\u4E0A\u4E0B\u6587\u91CC\u4E86\uFF0C\u76F4\u63A5\u7528\uFF1B\u8981\u4E48\u6362\u53C2\u6570\u3001\u6362\u4E00\u4E2A\u5DE5\u5177\uFF0C\u6362\u4E00\u79CD\u505A\u6CD5\u5F80\u4E0B\u8D70\u3002",
|
|
10690
|
+
"\u5982\u679C\u786E\u5B9E\u6CA1\u6709\u522B\u7684\u8DEF\uFF08\u5DE5\u5177\u672C\u8EAB\u574F\u4E86\u3001\u7F3A\u6743\u9650\u6216\u51ED\u636E\u3001\u9700\u8981\u7528\u6237\u5148\u505A\u70B9\u4EC0\u4E48\uFF09\uFF0C\u5C31\u505C\u4E0B\u6765\uFF0C\u76F4\u63A5\u544A\u8BC9\u7528\u6237\u5361\u5728\u54EA\u4E00\u6B65\u3001\u9700\u8981\u4ED6\u505A\u4EC0\u4E48\u3002",
|
|
10691
|
+
"\u8FD9\u662F\u552F\u4E00\u4E00\u6B21\u63D0\u9192\uFF1A\u518D\u9677\u8FDB\u540C\u6837\u7684\u91CD\u590D\uFF0C\u4EFB\u52A1\u4F1A\u88AB\u76F4\u63A5\u7EC8\u6B62\u3002"
|
|
10692
|
+
].join("\n");
|
|
10693
|
+
}
|
|
10445
10694
|
var CODE_VERIFICATION_POLICY = [
|
|
10446
10695
|
"<code_verification>",
|
|
10447
10696
|
"\u6539\u5B8C\u4EE3\u7801\u3001\u5BA3\u5E03\u5B8C\u6210\u4E4B\u524D\uFF0C\u8C03\u4E00\u6B21 `lsp_diagnostics`\uFF0C\u628A\u672C\u8F6E\u65B0\u5EFA\u6216\u4FEE\u6539\u8FC7\u7684\u6E90\u6587\u4EF6\u5217\u8FDB\u53BB\u3002",
|
|
@@ -10627,6 +10876,28 @@ var StallDetector = class {
|
|
|
10627
10876
|
}
|
|
10628
10877
|
return null;
|
|
10629
10878
|
}
|
|
10879
|
+
getStallDetail() {
|
|
10880
|
+
const failure = this.stalledFailure();
|
|
10881
|
+
if (failure) {
|
|
10882
|
+
return {
|
|
10883
|
+
kind: "failure",
|
|
10884
|
+
tool: failure.toolName,
|
|
10885
|
+
args: failure.args,
|
|
10886
|
+
count: failure.count,
|
|
10887
|
+
error: failure.error
|
|
10888
|
+
};
|
|
10889
|
+
}
|
|
10890
|
+
const streak = this.stalledStreak();
|
|
10891
|
+
if (streak) {
|
|
10892
|
+
return {
|
|
10893
|
+
kind: "streak",
|
|
10894
|
+
tool: streak.toolName,
|
|
10895
|
+
args: streak.args,
|
|
10896
|
+
count: streak.consecutive
|
|
10897
|
+
};
|
|
10898
|
+
}
|
|
10899
|
+
return null;
|
|
10900
|
+
}
|
|
10630
10901
|
reset() {
|
|
10631
10902
|
this.history = [];
|
|
10632
10903
|
this.failures.clear();
|
|
@@ -10945,7 +11216,7 @@ var ToolExecutor = class {
|
|
|
10945
11216
|
output: `\u64CD\u4F5C\u88AB hook \u963B\u6B62: ${blocked.reason || "\u672A\u77E5\u539F\u56E0"}`
|
|
10946
11217
|
});
|
|
10947
11218
|
}
|
|
10948
|
-
const gate = await this.checkPermission(et, tc.input);
|
|
11219
|
+
const gate = await this.checkPermission(et, tc.input, signal);
|
|
10949
11220
|
if (!gate.allowed) {
|
|
10950
11221
|
return finish({ ...base, success: false, output: gate.output });
|
|
10951
11222
|
}
|
|
@@ -11088,7 +11359,7 @@ var ToolExecutor = class {
|
|
|
11088
11359
|
);
|
|
11089
11360
|
return guessed || argsKey(args);
|
|
11090
11361
|
}
|
|
11091
|
-
async checkPermission(et, args) {
|
|
11362
|
+
async checkPermission(et, args, signal) {
|
|
11092
11363
|
const pm = this.deps.permissionManager;
|
|
11093
11364
|
if (!pm) return { allowed: true };
|
|
11094
11365
|
const toolName = et.name;
|
|
@@ -11122,6 +11393,9 @@ var ToolExecutor = class {
|
|
|
11122
11393
|
...decision.rememberable === false ? { rememberable: false } : {}
|
|
11123
11394
|
})
|
|
11124
11395
|
);
|
|
11396
|
+
if (signal?.aborted) {
|
|
11397
|
+
return { allowed: false, output: "\u64CD\u4F5C\u5DF2\u4E2D\u6B62\uFF1A\u7528\u6237\u7EC8\u6B62\u4E86\u8FD9\u4E00\u8F6E\u3002" };
|
|
11398
|
+
}
|
|
11125
11399
|
this.deps.telemetry?.counter(METRIC.TOOL_APPROVAL, 1, {
|
|
11126
11400
|
[GEN_AI.TOOL_NAME]: toolName,
|
|
11127
11401
|
"epoch.approval.outcome": outcome
|
|
@@ -11143,220 +11417,6 @@ var ToolExecutor = class {
|
|
|
11143
11417
|
return { allowed: true };
|
|
11144
11418
|
}
|
|
11145
11419
|
};
|
|
11146
|
-
var WINDOW_CHARS = 1024;
|
|
11147
|
-
var NGRAM = 16;
|
|
11148
|
-
var MAX_DISTINCT_RATIO = 0.2;
|
|
11149
|
-
var CHECK_EVERY_CHARS = 256;
|
|
11150
|
-
var SUSTAIN_CHECKS = 2;
|
|
11151
|
-
var MIN_NORMALIZED_CHARS = 256;
|
|
11152
|
-
function normalizeRuns(text) {
|
|
11153
|
-
return text.replace(/\s+/g, " ").replace(/(.)\1{3,}/gu, (_m, c) => c.repeat(3));
|
|
11154
|
-
}
|
|
11155
|
-
function distinctNgramRatio(text, n = NGRAM) {
|
|
11156
|
-
if (text.length < n) return 1;
|
|
11157
|
-
const seen = /* @__PURE__ */ new Set();
|
|
11158
|
-
let total = 0;
|
|
11159
|
-
for (let i = 0; i + n <= text.length; i++) {
|
|
11160
|
-
seen.add(text.slice(i, i + n));
|
|
11161
|
-
total++;
|
|
11162
|
-
}
|
|
11163
|
-
return total === 0 ? 1 : seen.size / total;
|
|
11164
|
-
}
|
|
11165
|
-
var RepetitionDetector = class {
|
|
11166
|
-
buf = "";
|
|
11167
|
-
sinceCheck = 0;
|
|
11168
|
-
streak = 0;
|
|
11169
|
-
chars = 0;
|
|
11170
|
-
hit = null;
|
|
11171
|
-
push(delta) {
|
|
11172
|
-
if (this.hit) return true;
|
|
11173
|
-
if (!delta) return false;
|
|
11174
|
-
this.chars += delta.length;
|
|
11175
|
-
this.buf = (this.buf + delta).slice(-WINDOW_CHARS);
|
|
11176
|
-
this.sinceCheck += delta.length;
|
|
11177
|
-
if (this.sinceCheck < CHECK_EVERY_CHARS) return false;
|
|
11178
|
-
this.sinceCheck = 0;
|
|
11179
|
-
if (this.buf.length < WINDOW_CHARS) return false;
|
|
11180
|
-
const norm = normalizeRuns(this.buf);
|
|
11181
|
-
if (norm.length < MIN_NORMALIZED_CHARS) {
|
|
11182
|
-
this.streak = 0;
|
|
11183
|
-
return false;
|
|
11184
|
-
}
|
|
11185
|
-
const ratio = distinctNgramRatio(norm);
|
|
11186
|
-
if (ratio > MAX_DISTINCT_RATIO) {
|
|
11187
|
-
this.streak = 0;
|
|
11188
|
-
return false;
|
|
11189
|
-
}
|
|
11190
|
-
if (++this.streak < SUSTAIN_CHECKS) return false;
|
|
11191
|
-
this.hit = { chars: this.chars, distinctRatio: ratio };
|
|
11192
|
-
return true;
|
|
11193
|
-
}
|
|
11194
|
-
pushAll(text) {
|
|
11195
|
-
for (let i = 0; i < text.length; i += CHECK_EVERY_CHARS) {
|
|
11196
|
-
if (this.push(text.slice(i, i + CHECK_EVERY_CHARS))) return true;
|
|
11197
|
-
}
|
|
11198
|
-
return false;
|
|
11199
|
-
}
|
|
11200
|
-
isLooping() {
|
|
11201
|
-
return this.hit !== null;
|
|
11202
|
-
}
|
|
11203
|
-
getInfo() {
|
|
11204
|
-
if (!this.hit) return null;
|
|
11205
|
-
return t("agent_run.looping_info", {
|
|
11206
|
-
chars: this.hit.chars,
|
|
11207
|
-
percent: Math.round(this.hit.distinctRatio * 100)
|
|
11208
|
-
});
|
|
11209
|
-
}
|
|
11210
|
-
};
|
|
11211
|
-
// src/agent/turn.ts
|
|
11212
|
-
async function* runTurn(opts) {
|
|
11213
|
-
if (opts.streaming) {
|
|
11214
|
-
return yield* runTurnStreaming(opts);
|
|
11215
|
-
}
|
|
11216
|
-
return yield* runTurnBuffered(opts);
|
|
11217
|
-
}
|
|
11218
|
-
async function* runTurnStreaming(opts) {
|
|
11219
|
-
const { provider, input, guard, reasoningGuard } = opts;
|
|
11220
|
-
const repetition = new RepetitionDetector();
|
|
11221
|
-
const reasoningRepetition = new RepetitionDetector();
|
|
11222
|
-
let text = "";
|
|
11223
|
-
let reasoningText = "";
|
|
11224
|
-
let finishReason = "stop";
|
|
11225
|
-
let looping;
|
|
11226
|
-
let usage;
|
|
11227
|
-
const toolCalls = [];
|
|
11228
|
-
let streamError;
|
|
11229
|
-
let model;
|
|
11230
|
-
let requestedModel;
|
|
11231
|
-
let warnings;
|
|
11232
|
-
const ac = new AbortController();
|
|
11233
|
-
const unlink = linkAbort(input.signal, ac);
|
|
11234
|
-
try {
|
|
11235
|
-
for await (const chunk of provider.stream({ ...input, signal: ac.signal })) {
|
|
11236
|
-
switch (chunk.type) {
|
|
11237
|
-
case "text-delta": {
|
|
11238
|
-
const raw = chunk.textDelta ?? "";
|
|
11239
|
-
if (!raw) break;
|
|
11240
|
-
const delta = guard ? guard.push(raw) : raw;
|
|
11241
|
-
if (!delta) break;
|
|
11242
|
-
text += delta;
|
|
11243
|
-
yield { type: "text-delta", text: delta };
|
|
11244
|
-
if (repetition.push(delta)) looping = repetition;
|
|
11245
|
-
break;
|
|
11246
|
-
}
|
|
11247
|
-
case "reasoning-delta": {
|
|
11248
|
-
const raw = chunk.textDelta ?? "";
|
|
11249
|
-
if (!raw) break;
|
|
11250
|
-
const delta = reasoningGuard ? reasoningGuard.push(raw) : raw;
|
|
11251
|
-
if (!delta) break;
|
|
11252
|
-
reasoningText += delta;
|
|
11253
|
-
yield { type: "reasoning-delta", text: delta };
|
|
11254
|
-
if (reasoningRepetition.push(delta)) looping = reasoningRepetition;
|
|
11255
|
-
break;
|
|
11256
|
-
}
|
|
11257
|
-
case "tool-call":
|
|
11258
|
-
if (chunk.toolCall) {
|
|
11259
|
-
toolCalls.push({
|
|
11260
|
-
toolCallId: chunk.toolCall.toolCallId,
|
|
11261
|
-
toolName: chunk.toolCall.toolName,
|
|
11262
|
-
input: chunk.toolCall.input
|
|
11263
|
-
});
|
|
11264
|
-
}
|
|
11265
|
-
break;
|
|
11266
|
-
case "finish":
|
|
11267
|
-
if (chunk.usage) usage = chunk.usage;
|
|
11268
|
-
if (chunk.finishReason) finishReason = chunk.finishReason;
|
|
11269
|
-
if (chunk.model) model = chunk.model;
|
|
11270
|
-
if (chunk.requestedModel) requestedModel = chunk.requestedModel;
|
|
11271
|
-
if (chunk.warnings) warnings = chunk.warnings;
|
|
11272
|
-
break;
|
|
11273
|
-
case "error":
|
|
11274
|
-
streamError = chunk.error ?? t("agent_run.unknown_provider_error");
|
|
11275
|
-
break;
|
|
11276
|
-
case "tool-result":
|
|
11277
|
-
break;
|
|
11278
|
-
}
|
|
11279
|
-
if (looping) {
|
|
11280
|
-
ac.abort();
|
|
11281
|
-
break;
|
|
11282
|
-
}
|
|
11283
|
-
}
|
|
11284
|
-
} finally {
|
|
11285
|
-
unlink();
|
|
11286
|
-
}
|
|
11287
|
-
if (streamError && !looping) throw new Error(streamError);
|
|
11288
|
-
if (guard) {
|
|
11289
|
-
const tail = guard.flush();
|
|
11290
|
-
if (tail) {
|
|
11291
|
-
text += tail;
|
|
11292
|
-
yield { type: "text-delta", text: tail };
|
|
11293
|
-
}
|
|
11294
|
-
}
|
|
11295
|
-
if (reasoningGuard) {
|
|
11296
|
-
const tail = reasoningGuard.flush();
|
|
11297
|
-
if (tail) {
|
|
11298
|
-
reasoningText += tail;
|
|
11299
|
-
yield { type: "reasoning-delta", text: tail };
|
|
11300
|
-
}
|
|
11301
|
-
}
|
|
11302
|
-
return {
|
|
11303
|
-
text,
|
|
11304
|
-
...usage ? { usage } : {},
|
|
11305
|
-
finishReason: looping ? "looping" : finishReason,
|
|
11306
|
-
...looping ? { loopingInfo: looping.getInfo() ?? "" } : {},
|
|
11307
|
-
...toolCalls.length > 0 ? { toolCalls } : {},
|
|
11308
|
-
...reasoningText ? { reasoningText } : {},
|
|
11309
|
-
...model ? { model } : {},
|
|
11310
|
-
...requestedModel ? { requestedModel } : {},
|
|
11311
|
-
...warnings && warnings.length > 0 ? { warnings } : {}
|
|
11312
|
-
};
|
|
11313
|
-
}
|
|
11314
|
-
async function* runTurnBuffered(opts) {
|
|
11315
|
-
const { provider, input, guard, reasoningGuard } = opts;
|
|
11316
|
-
const result = await provider.generate(input);
|
|
11317
|
-
const reasoningText = result.reasoningText ? filterWhole(reasoningGuard, result.reasoningText) : result.reasoningText;
|
|
11318
|
-
if (reasoningText) {
|
|
11319
|
-
yield { type: "reasoning-delta", text: reasoningText };
|
|
11320
|
-
}
|
|
11321
|
-
const text = filterWhole(guard, result.text);
|
|
11322
|
-
if (text) {
|
|
11323
|
-
yield { type: "text-delta", text };
|
|
11324
|
-
}
|
|
11325
|
-
const { reasoningText: _replaced, ...rest } = result;
|
|
11326
|
-
const textLoop = new RepetitionDetector();
|
|
11327
|
-
const reasoningLoop = new RepetitionDetector();
|
|
11328
|
-
textLoop.pushAll(text);
|
|
11329
|
-
if (reasoningText) reasoningLoop.pushAll(reasoningText);
|
|
11330
|
-
let looping;
|
|
11331
|
-
if (textLoop.isLooping()) looping = textLoop;
|
|
11332
|
-
else if (reasoningLoop.isLooping()) looping = reasoningLoop;
|
|
11333
|
-
return {
|
|
11334
|
-
...rest,
|
|
11335
|
-
text,
|
|
11336
|
-
...reasoningText ? { reasoningText } : {},
|
|
11337
|
-
...looping ? { finishReason: "looping", loopingInfo: looping.getInfo() ?? "" } : {}
|
|
11338
|
-
};
|
|
11339
|
-
}
|
|
11340
|
-
function linkAbort(outer, ac) {
|
|
11341
|
-
if (!outer) return () => {
|
|
11342
|
-
};
|
|
11343
|
-
if (outer.aborted) {
|
|
11344
|
-
ac.abort(outer.reason);
|
|
11345
|
-
return () => {
|
|
11346
|
-
};
|
|
11347
|
-
}
|
|
11348
|
-
const onAbort = () => ac.abort(outer.reason);
|
|
11349
|
-
outer.addEventListener("abort", onAbort, { once: true });
|
|
11350
|
-
return () => outer.removeEventListener("abort", onAbort);
|
|
11351
|
-
}
|
|
11352
|
-
function filterWhole(guard, text) {
|
|
11353
|
-
if (!guard || !text) return text;
|
|
11354
|
-
return guard.push(text) + guard.flush();
|
|
11355
|
-
}
|
|
11356
|
-
// src/agent/types.ts
|
|
11357
|
-
function isAbortSignal(signal) {
|
|
11358
|
-
return !!signal && typeof signal.addEventListener === "function" && typeof signal.removeEventListener === "function";
|
|
11359
|
-
}
|
|
11360
11420
|
// src/agent/loop.ts
|
|
11361
11421
|
var DEFAULT_CONTEXT_LENGTH = 128e3;
|
|
11362
11422
|
function shrank(before, after) {
|
|
@@ -11683,6 +11743,7 @@ var AgentLoop = class {
|
|
|
11683
11743
|
let idleNudged = false;
|
|
11684
11744
|
let blankNudged = false;
|
|
11685
11745
|
let blankAfterNudge = false;
|
|
11746
|
+
let stallNudged = false;
|
|
11686
11747
|
for (let turn = 1; turn <= maxTurns; turn++) {
|
|
11687
11748
|
if (this.isAborted(input.signal)) {
|
|
11688
11749
|
if (turns.length > 0 || lastText) {
|
|
@@ -12081,7 +12142,17 @@ ${finalText}` };
|
|
|
12081
12142
|
turn
|
|
12082
12143
|
});
|
|
12083
12144
|
yield { type: "turn-complete", turn };
|
|
12084
|
-
|
|
12145
|
+
const stall = this.stallDetector.getStallDetail();
|
|
12146
|
+
if (stall && !stallNudged && turn < maxTurns) {
|
|
12147
|
+
stallNudged = true;
|
|
12148
|
+
yield {
|
|
12149
|
+
type: "notice",
|
|
12150
|
+
code: "stall-nudged",
|
|
12151
|
+
text: t("agent_run.stall_nudged", { info: this.stallDetector.getStallInfo() ?? "" })
|
|
12152
|
+
};
|
|
12153
|
+
this.stallDetector.reset();
|
|
12154
|
+
messages.push({ role: "user", content: systemNote(stallNote(stall)) });
|
|
12155
|
+
} else if (stall) {
|
|
12085
12156
|
const msg2 = t("agent_run.stalled", { info: this.stallDetector.getStallInfo() ?? "" });
|
|
12086
12157
|
yield { type: "text-delta", text: msg2 };
|
|
12087
12158
|
await this.prompt.syncMemory(userText, msg2);
|