@theokit/sdk 2.13.1 → 2.15.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/CHANGELOG.md +12 -0
- package/dist/a2a/index.cjs +242 -3
- package/dist/a2a/index.cjs.map +1 -1
- package/dist/a2a/index.js +242 -3
- package/dist/a2a/index.js.map +1 -1
- package/dist/{cron-CpxLdAXc.d.cts → cron-BxLSz1UH.d.cts} +1 -1
- package/dist/{cron-CL_9nfhQ.d.ts → cron-DcaoP7aW.d.ts} +1 -1
- package/dist/cron.cjs +225 -3
- package/dist/cron.cjs.map +1 -1
- package/dist/cron.d.cts +2 -2
- package/dist/cron.d.ts +2 -2
- package/dist/cron.js +225 -3
- package/dist/cron.js.map +1 -1
- package/dist/define-tool.d.ts +8 -0
- package/dist/{errors-9yw4UQwX.d.cts → errors-Bart0ptP.d.cts} +1 -1
- package/dist/{errors-DFiY-NHK.d.ts → errors-DJuuubJK.d.ts} +1 -1
- package/dist/errors.d.cts +2 -2
- package/dist/eval.cjs +228 -6
- package/dist/eval.cjs.map +1 -1
- package/dist/eval.js +228 -6
- package/dist/eval.js.map +1 -1
- package/dist/index.cjs +230 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +41 -6
- package/dist/index.d.ts +41 -6
- package/dist/index.js +230 -4
- package/dist/index.js.map +1 -1
- package/dist/internal/agent-loop/doom-loop-tracker.d.ts +22 -0
- package/dist/internal/agent-loop/loop-types.d.ts +6 -0
- package/dist/internal/llm/hermes-tool-extract.d.ts +1 -0
- package/dist/{run-TMdc7gmo.d.cts → run-DXy_MVwz.d.cts} +29 -1
- package/dist/{run-TMdc7gmo.d.ts → run-DXy_MVwz.d.ts} +29 -1
- package/dist/sanitize/coerce.d.cts +1 -0
- package/dist/sanitize/coerce.d.ts +1 -0
- package/dist/sanitize/index.cjs +119 -0
- package/dist/sanitize/index.cjs.map +1 -0
- package/dist/sanitize/index.d.cts +9 -0
- package/dist/sanitize/index.d.ts +9 -0
- package/dist/sanitize/index.js +116 -0
- package/dist/sanitize/index.js.map +1 -0
- package/dist/sanitize/sanitize-tool-input.d.cts +11 -0
- package/dist/sanitize/sanitize-tool-input.d.ts +11 -0
- package/dist/sanitize/types.d.cts +39 -0
- package/dist/sanitize/types.d.ts +39 -0
- package/dist/types/run.d.ts +28 -0
- package/package.json +13 -2
package/dist/a2a/index.js
CHANGED
|
@@ -2333,6 +2333,7 @@ function applyScriptMetrics(base, script) {
|
|
|
2333
2333
|
if (script.usage !== void 0) base.usage = script.usage;
|
|
2334
2334
|
if (script.cost !== void 0) base.cost = script.cost;
|
|
2335
2335
|
if (script.stoppedAtIterationLimit === true) base.stoppedAtIterationLimit = true;
|
|
2336
|
+
if (script.stoppedByDoomLoop === true) base.stoppedByDoomLoop = true;
|
|
2336
2337
|
}
|
|
2337
2338
|
var FixtureRunBase;
|
|
2338
2339
|
var init_fixture_run_base = __esm({
|
|
@@ -6809,6 +6810,98 @@ var init_budget_gate = __esm({
|
|
|
6809
6810
|
}
|
|
6810
6811
|
});
|
|
6811
6812
|
|
|
6813
|
+
// src/internal/agent-loop/doom-loop-tracker.ts
|
|
6814
|
+
function createDoomLoopTracker(option) {
|
|
6815
|
+
if (option === false) return void 0;
|
|
6816
|
+
return new DoomLoopTracker(option);
|
|
6817
|
+
}
|
|
6818
|
+
function assertValidThresholds(soft, hard) {
|
|
6819
|
+
for (const [label, value] of [
|
|
6820
|
+
["softThreshold", soft],
|
|
6821
|
+
["hardThreshold", hard]
|
|
6822
|
+
]) {
|
|
6823
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
6824
|
+
throw new ConfigurationError(
|
|
6825
|
+
`doomLoop.${label} must be a positive integer (received ${value}).`,
|
|
6826
|
+
{ code: "invalid_doom_loop_threshold" }
|
|
6827
|
+
);
|
|
6828
|
+
}
|
|
6829
|
+
}
|
|
6830
|
+
}
|
|
6831
|
+
function sortKeys(value) {
|
|
6832
|
+
if (value === null || typeof value !== "object") return value;
|
|
6833
|
+
if (Array.isArray(value)) return value.map(sortKeys);
|
|
6834
|
+
const out = {};
|
|
6835
|
+
for (const key of Object.keys(value).sort()) {
|
|
6836
|
+
out[key] = sortKeys(value[key]);
|
|
6837
|
+
}
|
|
6838
|
+
return out;
|
|
6839
|
+
}
|
|
6840
|
+
function signatureOf(call) {
|
|
6841
|
+
const { input } = call;
|
|
6842
|
+
let inputSig;
|
|
6843
|
+
if (input === null || input === void 0) inputSig = "null";
|
|
6844
|
+
else if (typeof input !== "object") inputSig = String(input);
|
|
6845
|
+
else {
|
|
6846
|
+
try {
|
|
6847
|
+
inputSig = JSON.stringify(sortKeys(input)) ?? "null";
|
|
6848
|
+
} catch {
|
|
6849
|
+
inputSig = String(input);
|
|
6850
|
+
}
|
|
6851
|
+
}
|
|
6852
|
+
return `${call.name}\0${inputSig}`;
|
|
6853
|
+
}
|
|
6854
|
+
function firstDoomLoopVerdict(tracker, calls) {
|
|
6855
|
+
let escalation = { kind: "ok" };
|
|
6856
|
+
for (const call of calls) {
|
|
6857
|
+
const v = tracker.inspect(call);
|
|
6858
|
+
if (v.kind === "hard") return v;
|
|
6859
|
+
if (v.kind === "soft" && escalation.kind === "ok") escalation = v;
|
|
6860
|
+
}
|
|
6861
|
+
return escalation;
|
|
6862
|
+
}
|
|
6863
|
+
var DEFAULT_CONFIG, DoomLoopTracker;
|
|
6864
|
+
var init_doom_loop_tracker = __esm({
|
|
6865
|
+
"src/internal/agent-loop/doom-loop-tracker.ts"() {
|
|
6866
|
+
init_errors();
|
|
6867
|
+
DEFAULT_CONFIG = { softThreshold: 3, hardThreshold: 5 };
|
|
6868
|
+
DoomLoopTracker = class {
|
|
6869
|
+
#config;
|
|
6870
|
+
#lastSignature = "";
|
|
6871
|
+
#count = 0;
|
|
6872
|
+
constructor(config) {
|
|
6873
|
+
const softThreshold = config?.softThreshold ?? DEFAULT_CONFIG.softThreshold;
|
|
6874
|
+
const hardThreshold = config?.hardThreshold ?? DEFAULT_CONFIG.hardThreshold;
|
|
6875
|
+
assertValidThresholds(softThreshold, hardThreshold);
|
|
6876
|
+
this.#config = { softThreshold, hardThreshold };
|
|
6877
|
+
}
|
|
6878
|
+
inspect(call) {
|
|
6879
|
+
const signature = signatureOf(call);
|
|
6880
|
+
this.#count = signature === this.#lastSignature ? this.#count + 1 : 1;
|
|
6881
|
+
this.#lastSignature = signature;
|
|
6882
|
+
const count = this.#count;
|
|
6883
|
+
if (count >= this.#config.hardThreshold) {
|
|
6884
|
+
return {
|
|
6885
|
+
kind: "hard",
|
|
6886
|
+
message: `Detected ${count} consecutive identical calls to \`${call.name}\`; stopping to avoid a loop.`
|
|
6887
|
+
};
|
|
6888
|
+
}
|
|
6889
|
+
if (count === this.#config.softThreshold) {
|
|
6890
|
+
return {
|
|
6891
|
+
kind: "soft",
|
|
6892
|
+
message: `Detected ${count} consecutive identical calls to \`${call.name}\`; try a different approach.`
|
|
6893
|
+
};
|
|
6894
|
+
}
|
|
6895
|
+
return { kind: "ok" };
|
|
6896
|
+
}
|
|
6897
|
+
reset() {
|
|
6898
|
+
this.#lastSignature = "";
|
|
6899
|
+
this.#count = 0;
|
|
6900
|
+
}
|
|
6901
|
+
};
|
|
6902
|
+
}
|
|
6903
|
+
});
|
|
6904
|
+
|
|
6812
6905
|
// src/internal/budget/usage-accumulator.ts
|
|
6813
6906
|
var UsageAccumulator;
|
|
6814
6907
|
var init_usage_accumulator = __esm({
|
|
@@ -6998,6 +7091,7 @@ async function initLoopContext(inputs) {
|
|
|
6998
7091
|
tools,
|
|
6999
7092
|
finalText: "",
|
|
7000
7093
|
finalStatus: "finished",
|
|
7094
|
+
doomLoop: createDoomLoopTracker(inputs.doomLoop),
|
|
7001
7095
|
usage: new UsageAccumulator(),
|
|
7002
7096
|
nudgeAttempts: 0,
|
|
7003
7097
|
stopFeedbackAttempts: 0,
|
|
@@ -7050,6 +7144,7 @@ function sanitize(name) {
|
|
|
7050
7144
|
var init_loop_context_init = __esm({
|
|
7051
7145
|
"src/internal/agent-loop/loop-context-init.ts"() {
|
|
7052
7146
|
init_usage_accumulator();
|
|
7147
|
+
init_doom_loop_tracker();
|
|
7053
7148
|
init_message_builders();
|
|
7054
7149
|
}
|
|
7055
7150
|
});
|
|
@@ -8162,6 +8257,7 @@ async function runAgentLoop(inputs) {
|
|
|
8162
8257
|
ctx.finalStatus = "error";
|
|
8163
8258
|
}
|
|
8164
8259
|
sendSpan?.setAttribute("status", ctx.finalStatus);
|
|
8260
|
+
if (ctx.stoppedByDoomLoop === true) sendSpan?.setAttribute("stoppedByDoomLoop", true);
|
|
8165
8261
|
if (inputs.telemetry?.includeContent === true && ctx.finalText.length > 0) {
|
|
8166
8262
|
sendSpan?.addEvent("response", { content: ctx.finalText });
|
|
8167
8263
|
}
|
|
@@ -8188,7 +8284,8 @@ async function runAgentLoop(inputs) {
|
|
|
8188
8284
|
...usage !== void 0 ? { usage } : {},
|
|
8189
8285
|
...cost !== void 0 ? { cost } : {},
|
|
8190
8286
|
...ctx.error !== void 0 ? { error: ctx.error } : {},
|
|
8191
|
-
...ctx.stoppedAtIterationLimit === true ? { stoppedAtIterationLimit: true } : {}
|
|
8287
|
+
...ctx.stoppedAtIterationLimit === true ? { stoppedAtIterationLimit: true } : {},
|
|
8288
|
+
...ctx.stoppedByDoomLoop === true ? { stoppedByDoomLoop: true } : {}
|
|
8192
8289
|
};
|
|
8193
8290
|
} finally {
|
|
8194
8291
|
if (ctxRef !== void 0 && ctxRef.memoryProviderHandle !== void 0 && inputs.memoryProvider !== void 0) {
|
|
@@ -8347,8 +8444,26 @@ async function continueOrTerminate(inputs, ctx, llmOutput) {
|
|
|
8347
8444
|
}
|
|
8348
8445
|
}
|
|
8349
8446
|
pushToolConversationSteps(ctx, llmOutput.toolCalls, toolResults);
|
|
8447
|
+
if (await inspectDoomLoop(inputs, ctx, llmOutput.toolCalls) === "stop") return "done";
|
|
8350
8448
|
return handleToolErrorContinuation(inputs, ctx, toolResults);
|
|
8351
8449
|
}
|
|
8450
|
+
async function inspectDoomLoop(inputs, ctx, toolCalls) {
|
|
8451
|
+
if (ctx.doomLoop === void 0) return "continue";
|
|
8452
|
+
const verdict = firstDoomLoopVerdict(ctx.doomLoop, toolCalls);
|
|
8453
|
+
if (verdict.kind === "hard") {
|
|
8454
|
+
ctx.stoppedByDoomLoop = true;
|
|
8455
|
+
await emitAssistantTextStep(
|
|
8456
|
+
inputs,
|
|
8457
|
+
ctx,
|
|
8458
|
+
verdict.message ?? "Stopped: repeated identical tool calls made no progress."
|
|
8459
|
+
);
|
|
8460
|
+
return "stop";
|
|
8461
|
+
}
|
|
8462
|
+
if (verdict.kind === "soft") {
|
|
8463
|
+
ctx.messages.push({ role: "user", content: [{ type: "text", text: verdict.message ?? "" }] });
|
|
8464
|
+
}
|
|
8465
|
+
return "continue";
|
|
8466
|
+
}
|
|
8352
8467
|
var MAX_NUDGE_ATTEMPTS, MAX_STOP_FEEDBACK_ATTEMPTS;
|
|
8353
8468
|
var init_loop = __esm({
|
|
8354
8469
|
"src/internal/agent-loop/loop.ts"() {
|
|
@@ -8356,6 +8471,7 @@ var init_loop = __esm({
|
|
|
8356
8471
|
init_safe_call();
|
|
8357
8472
|
init_validate_response();
|
|
8358
8473
|
init_budget_gate();
|
|
8474
|
+
init_doom_loop_tracker();
|
|
8359
8475
|
init_loop_context_init();
|
|
8360
8476
|
init_loop_llm_stream();
|
|
8361
8477
|
init_message_builders();
|
|
@@ -10093,6 +10209,124 @@ var init_ollama_native = __esm({
|
|
|
10093
10209
|
ollamaSystemText = collapseSystemText;
|
|
10094
10210
|
}
|
|
10095
10211
|
});
|
|
10212
|
+
function loadJsonrepair() {
|
|
10213
|
+
if (cachedJsonrepair === void 0) {
|
|
10214
|
+
const req = createRequire(import.meta.url);
|
|
10215
|
+
cachedJsonrepair = req("jsonrepair").jsonrepair;
|
|
10216
|
+
}
|
|
10217
|
+
return cachedJsonrepair;
|
|
10218
|
+
}
|
|
10219
|
+
function isPlainObject(v) {
|
|
10220
|
+
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
10221
|
+
}
|
|
10222
|
+
function toFiniteNumber(raw) {
|
|
10223
|
+
if (raw === "") return void 0;
|
|
10224
|
+
const n = Number(raw);
|
|
10225
|
+
return Number.isFinite(n) && String(n) === raw ? n : void 0;
|
|
10226
|
+
}
|
|
10227
|
+
function tryJson(raw, repair) {
|
|
10228
|
+
const t = raw.trimStart();
|
|
10229
|
+
if (!(t.startsWith("{") || t.startsWith("["))) return void 0;
|
|
10230
|
+
try {
|
|
10231
|
+
return JSON.parse(repair ? loadJsonrepair()(t) : t);
|
|
10232
|
+
} catch {
|
|
10233
|
+
return void 0;
|
|
10234
|
+
}
|
|
10235
|
+
}
|
|
10236
|
+
function heuristicCoerce(raw, repairJson) {
|
|
10237
|
+
if (raw === "true") return true;
|
|
10238
|
+
if (raw === "false") return false;
|
|
10239
|
+
if (raw === "null") return null;
|
|
10240
|
+
const n = toFiniteNumber(raw);
|
|
10241
|
+
if (n !== void 0) return n;
|
|
10242
|
+
const json = tryJson(raw, false) ?? (repairJson ? tryJson(raw, true) : void 0);
|
|
10243
|
+
return json === void 0 ? raw : json;
|
|
10244
|
+
}
|
|
10245
|
+
function coerceCandidates(raw, repairJson) {
|
|
10246
|
+
const out = [];
|
|
10247
|
+
if (raw === "true") out.push(true);
|
|
10248
|
+
else if (raw === "false") out.push(false);
|
|
10249
|
+
else if (raw === "null") out.push(null);
|
|
10250
|
+
const n = toFiniteNumber(raw);
|
|
10251
|
+
if (n !== void 0) out.push(n);
|
|
10252
|
+
const json = tryJson(raw, false) ?? (repairJson ? tryJson(raw, true) : void 0);
|
|
10253
|
+
if (json !== void 0) out.push(json);
|
|
10254
|
+
out.push(raw);
|
|
10255
|
+
return out;
|
|
10256
|
+
}
|
|
10257
|
+
function objectShape(schema) {
|
|
10258
|
+
const shape = schema?.shape;
|
|
10259
|
+
return shape !== null && typeof shape === "object" ? shape : void 0;
|
|
10260
|
+
}
|
|
10261
|
+
var cachedJsonrepair;
|
|
10262
|
+
var init_coerce = __esm({
|
|
10263
|
+
"src/sanitize/coerce.ts"() {
|
|
10264
|
+
}
|
|
10265
|
+
});
|
|
10266
|
+
|
|
10267
|
+
// src/sanitize/sanitize-tool-input.ts
|
|
10268
|
+
function applyTrim(key, value, ctx) {
|
|
10269
|
+
const trimmed = value.trim();
|
|
10270
|
+
if (trimmed !== value) ctx.notes.push(`trimmed "${key}"`);
|
|
10271
|
+
return trimmed;
|
|
10272
|
+
}
|
|
10273
|
+
function applyCoerce(key, raw, ctx) {
|
|
10274
|
+
const field = ctx.shape?.[key];
|
|
10275
|
+
let coerced = raw;
|
|
10276
|
+
if (field) {
|
|
10277
|
+
for (const candidate of coerceCandidates(raw, ctx.repairJson)) {
|
|
10278
|
+
if (field.safeParse(candidate).success) {
|
|
10279
|
+
coerced = candidate;
|
|
10280
|
+
break;
|
|
10281
|
+
}
|
|
10282
|
+
}
|
|
10283
|
+
} else {
|
|
10284
|
+
coerced = heuristicCoerce(raw, ctx.repairJson);
|
|
10285
|
+
}
|
|
10286
|
+
if (coerced !== raw) ctx.notes.push(`coerced "${key}"`);
|
|
10287
|
+
return coerced;
|
|
10288
|
+
}
|
|
10289
|
+
function applyRepair(key, value, ctx) {
|
|
10290
|
+
const repaired = tryJson(value, true);
|
|
10291
|
+
if (repaired === void 0) return value;
|
|
10292
|
+
ctx.notes.push(`repaired json "${key}"`);
|
|
10293
|
+
return repaired;
|
|
10294
|
+
}
|
|
10295
|
+
function sanitizeString(key, value, ctx) {
|
|
10296
|
+
let out = ctx.trim ? applyTrim(key, value, ctx) : value;
|
|
10297
|
+
if (ctx.coerce && typeof out === "string") out = applyCoerce(key, out, ctx);
|
|
10298
|
+
if (ctx.repairJson && !ctx.coerce && typeof out === "string") out = applyRepair(key, out, ctx);
|
|
10299
|
+
return out;
|
|
10300
|
+
}
|
|
10301
|
+
function walk(input, ctx, depth) {
|
|
10302
|
+
const out = {};
|
|
10303
|
+
for (const [key, value] of Object.entries(input)) {
|
|
10304
|
+
if (typeof value === "string") out[key] = sanitizeString(key, value, ctx);
|
|
10305
|
+
else if (ctx.deep && depth < ctx.maxDepth && isPlainObject(value))
|
|
10306
|
+
out[key] = walk(value, ctx, depth + 1);
|
|
10307
|
+
else out[key] = value;
|
|
10308
|
+
}
|
|
10309
|
+
return out;
|
|
10310
|
+
}
|
|
10311
|
+
function sanitizeToolInput(input, options) {
|
|
10312
|
+
if (!isPlainObject(input)) return { value: input, changed: false, notes: [] };
|
|
10313
|
+
const ctx = {
|
|
10314
|
+
trim: options?.trim,
|
|
10315
|
+
coerce: options?.coerce ?? false,
|
|
10316
|
+
repairJson: options?.repairJson ?? false,
|
|
10317
|
+
deep: options?.deep ?? false,
|
|
10318
|
+
maxDepth: options?.maxDepth ?? 8,
|
|
10319
|
+
shape: objectShape(options?.schema),
|
|
10320
|
+
notes: []
|
|
10321
|
+
};
|
|
10322
|
+
const value = walk(input, ctx, 0);
|
|
10323
|
+
return { value, changed: ctx.notes.length > 0, notes: ctx.notes };
|
|
10324
|
+
}
|
|
10325
|
+
var init_sanitize_tool_input = __esm({
|
|
10326
|
+
"src/sanitize/sanitize-tool-input.ts"() {
|
|
10327
|
+
init_coerce();
|
|
10328
|
+
}
|
|
10329
|
+
});
|
|
10096
10330
|
|
|
10097
10331
|
// src/internal/llm/hermes-tool-extract.ts
|
|
10098
10332
|
function extractHermesToolCalls(content, makeId) {
|
|
@@ -10116,13 +10350,14 @@ function parseHermesParams(inner) {
|
|
|
10116
10350
|
const key = param[1];
|
|
10117
10351
|
const value = param[2];
|
|
10118
10352
|
if (key === void 0 || value === void 0) continue;
|
|
10119
|
-
input[key.trim()] = value
|
|
10353
|
+
input[key.trim()] = value;
|
|
10120
10354
|
}
|
|
10121
|
-
return input;
|
|
10355
|
+
return sanitizeToolInput(input, { trim: true }).value;
|
|
10122
10356
|
}
|
|
10123
10357
|
var HERMES_BLOCK, HERMES_PARAM;
|
|
10124
10358
|
var init_hermes_tool_extract = __esm({
|
|
10125
10359
|
"src/internal/llm/hermes-tool-extract.ts"() {
|
|
10360
|
+
init_sanitize_tool_input();
|
|
10126
10361
|
HERMES_BLOCK = /<function=\s*([^>\s]+)\s*>([\s\S]*?)<\/tool_call>/g;
|
|
10127
10362
|
HERMES_PARAM = /<parameter=\s*([^>\s]+)\s*>([\s\S]*?)<\/parameter>/g;
|
|
10128
10363
|
}
|
|
@@ -11321,6 +11556,8 @@ function buildLoopInputs(options, runId, userText) {
|
|
|
11321
11556
|
// M1-2: per-send iteration ceiling (validated above). The loop reads
|
|
11322
11557
|
// inputs.maxIterations (default 8 when unset).
|
|
11323
11558
|
...maxIterations !== void 0 ? { maxIterations } : {},
|
|
11559
|
+
// Doom-loop guard config (default on; `false` disables, object tunes thresholds).
|
|
11560
|
+
...options.sendOptions.doomLoop !== void 0 ? { doomLoop: options.sendOptions.doomLoop } : {},
|
|
11324
11561
|
// D315-D317 — tool lifecycle hooks (cost tracking + audit + retry/alert)
|
|
11325
11562
|
...options.agentOptions.onToolStart !== void 0 ? { onToolStart: options.agentOptions.onToolStart } : {},
|
|
11326
11563
|
...options.agentOptions.onToolEnd !== void 0 ? { onToolEnd: options.agentOptions.onToolEnd } : {},
|
|
@@ -11469,6 +11706,7 @@ var init_real_local_run = __esm({
|
|
|
11469
11706
|
if (output.usage !== void 0) this.script.usage = output.usage;
|
|
11470
11707
|
if (output.cost !== void 0) this.script.cost = output.cost;
|
|
11471
11708
|
if (output.stoppedAtIterationLimit === true) this.script.stoppedAtIterationLimit = true;
|
|
11709
|
+
if (output.stoppedByDoomLoop === true) this.script.stoppedByDoomLoop = true;
|
|
11472
11710
|
if (output.error !== void 0 && this.script.errorDetail === void 0) {
|
|
11473
11711
|
this.script.errorDetail = {
|
|
11474
11712
|
message: output.error.message,
|
|
@@ -14460,6 +14698,7 @@ function isEmptyRound(result) {
|
|
|
14460
14698
|
return (result.result ?? "").trim() === "";
|
|
14461
14699
|
}
|
|
14462
14700
|
function classifyRound(result, round, maxRounds, emptyStreak) {
|
|
14701
|
+
if (result.stoppedByDoomLoop === true) return "no_progress";
|
|
14463
14702
|
if (result.stoppedAtIterationLimit !== true) return "done";
|
|
14464
14703
|
if (isEmptyRound(result) && emptyStreak >= 1) return "no_progress";
|
|
14465
14704
|
if (round >= maxRounds) return "step_limit";
|