@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.
Files changed (3) hide show
  1. package/dist/index.d.ts +7439 -7426
  2. package/dist/index.js +316 -245
  3. 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, _ctx) {
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" } : result.errorType === "timeout" ? { suggestion: "\u4F18\u5316\u7B97\u6CD5\uFF0C\u6216\u63D0\u9AD8 timeout\uFF08\u4E0A\u9650 120 \u79D2\uFF09" } : {}
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 this.runWithTimeout(
1204
- this.runner(task.goal, task.context ?? "", sessionId, task.role, parentSessionId),
1205
- this.config.task_timeout
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
- goal: task.goal,
1219
- success: false,
1220
- summary: "",
1221
- error: msg.includes("\u8D85\u65F6") ? `[\u8D85\u65F6] ${msg}` : msg,
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: "\u90E8\u5206\u5B50\u4EFB\u52A1\u5931\u8D25\uFF0C\u8BE6\u60C5\u89C1\u8F93\u51FA" } },
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
- if (this.stallDetector.isStalled()) {
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);