@theokit/sdk 2.14.0 → 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/dist/cron.d.cts CHANGED
@@ -1,2 +1,2 @@
1
- import './run-TMdc7gmo.cjs';
2
- export { K as Cron } from './cron-CpxLdAXc.cjs';
1
+ import './run-DXy_MVwz.cjs';
2
+ export { K as Cron } from './cron-BxLSz1UH.cjs';
package/dist/cron.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import './run-TMdc7gmo.js';
2
- export { K as Cron } from './cron-CL_9nfhQ.js';
1
+ import './run-DXy_MVwz.js';
2
+ export { K as Cron } from './cron-DcaoP7aW.js';
package/dist/cron.js CHANGED
@@ -1455,6 +1455,7 @@ function isEmptyRound(result) {
1455
1455
  return (result.result ?? "").trim() === "";
1456
1456
  }
1457
1457
  function classifyRound(result, round, maxRounds, emptyStreak) {
1458
+ if (result.stoppedByDoomLoop === true) return "no_progress";
1458
1459
  if (result.stoppedAtIterationLimit !== true) return "done";
1459
1460
  if (isEmptyRound(result) && emptyStreak >= 1) return "no_progress";
1460
1461
  if (round >= maxRounds) return "step_limit";
@@ -4492,6 +4493,7 @@ function applyScriptMetrics(base, script) {
4492
4493
  if (script.usage !== void 0) base.usage = script.usage;
4493
4494
  if (script.cost !== void 0) base.cost = script.cost;
4494
4495
  if (script.stoppedAtIterationLimit === true) base.stoppedAtIterationLimit = true;
4496
+ if (script.stoppedByDoomLoop === true) base.stoppedByDoomLoop = true;
4495
4497
  }
4496
4498
 
4497
4499
  // src/internal/runtime/cloud/cloud-run.ts
@@ -8273,6 +8275,93 @@ function evaluateBudgetGate(tracker) {
8273
8275
  }
8274
8276
  }
8275
8277
 
8278
+ // src/internal/agent-loop/doom-loop-tracker.ts
8279
+ init_errors();
8280
+ function createDoomLoopTracker(option) {
8281
+ if (option === false) return void 0;
8282
+ return new DoomLoopTracker(option);
8283
+ }
8284
+ var DEFAULT_CONFIG = { softThreshold: 3, hardThreshold: 5 };
8285
+ function assertValidThresholds(soft, hard) {
8286
+ for (const [label, value] of [
8287
+ ["softThreshold", soft],
8288
+ ["hardThreshold", hard]
8289
+ ]) {
8290
+ if (!Number.isInteger(value) || value < 1) {
8291
+ throw new ConfigurationError(
8292
+ `doomLoop.${label} must be a positive integer (received ${value}).`,
8293
+ { code: "invalid_doom_loop_threshold" }
8294
+ );
8295
+ }
8296
+ }
8297
+ }
8298
+ function sortKeys(value) {
8299
+ if (value === null || typeof value !== "object") return value;
8300
+ if (Array.isArray(value)) return value.map(sortKeys);
8301
+ const out = {};
8302
+ for (const key of Object.keys(value).sort()) {
8303
+ out[key] = sortKeys(value[key]);
8304
+ }
8305
+ return out;
8306
+ }
8307
+ function signatureOf(call) {
8308
+ const { input } = call;
8309
+ let inputSig;
8310
+ if (input === null || input === void 0) inputSig = "null";
8311
+ else if (typeof input !== "object") inputSig = String(input);
8312
+ else {
8313
+ try {
8314
+ inputSig = JSON.stringify(sortKeys(input)) ?? "null";
8315
+ } catch {
8316
+ inputSig = String(input);
8317
+ }
8318
+ }
8319
+ return `${call.name}\0${inputSig}`;
8320
+ }
8321
+ var DoomLoopTracker = class {
8322
+ #config;
8323
+ #lastSignature = "";
8324
+ #count = 0;
8325
+ constructor(config) {
8326
+ const softThreshold = config?.softThreshold ?? DEFAULT_CONFIG.softThreshold;
8327
+ const hardThreshold = config?.hardThreshold ?? DEFAULT_CONFIG.hardThreshold;
8328
+ assertValidThresholds(softThreshold, hardThreshold);
8329
+ this.#config = { softThreshold, hardThreshold };
8330
+ }
8331
+ inspect(call) {
8332
+ const signature = signatureOf(call);
8333
+ this.#count = signature === this.#lastSignature ? this.#count + 1 : 1;
8334
+ this.#lastSignature = signature;
8335
+ const count = this.#count;
8336
+ if (count >= this.#config.hardThreshold) {
8337
+ return {
8338
+ kind: "hard",
8339
+ message: `Detected ${count} consecutive identical calls to \`${call.name}\`; stopping to avoid a loop.`
8340
+ };
8341
+ }
8342
+ if (count === this.#config.softThreshold) {
8343
+ return {
8344
+ kind: "soft",
8345
+ message: `Detected ${count} consecutive identical calls to \`${call.name}\`; try a different approach.`
8346
+ };
8347
+ }
8348
+ return { kind: "ok" };
8349
+ }
8350
+ reset() {
8351
+ this.#lastSignature = "";
8352
+ this.#count = 0;
8353
+ }
8354
+ };
8355
+ function firstDoomLoopVerdict(tracker, calls) {
8356
+ let escalation = { kind: "ok" };
8357
+ for (const call of calls) {
8358
+ const v = tracker.inspect(call);
8359
+ if (v.kind === "hard") return v;
8360
+ if (v.kind === "soft" && escalation.kind === "ok") escalation = v;
8361
+ }
8362
+ return escalation;
8363
+ }
8364
+
8276
8365
  // src/internal/budget/usage-accumulator.ts
8277
8366
  var UsageAccumulator = class {
8278
8367
  input = 0;
@@ -8453,6 +8542,7 @@ async function initLoopContext(inputs) {
8453
8542
  tools,
8454
8543
  finalText: "",
8455
8544
  finalStatus: "finished",
8545
+ doomLoop: createDoomLoopTracker(inputs.doomLoop),
8456
8546
  usage: new UsageAccumulator(),
8457
8547
  nudgeAttempts: 0,
8458
8548
  stopFeedbackAttempts: 0,
@@ -9477,6 +9567,7 @@ async function runAgentLoop(inputs) {
9477
9567
  ctx.finalStatus = "error";
9478
9568
  }
9479
9569
  sendSpan?.setAttribute("status", ctx.finalStatus);
9570
+ if (ctx.stoppedByDoomLoop === true) sendSpan?.setAttribute("stoppedByDoomLoop", true);
9480
9571
  if (inputs.telemetry?.includeContent === true && ctx.finalText.length > 0) {
9481
9572
  sendSpan?.addEvent("response", { content: ctx.finalText });
9482
9573
  }
@@ -9503,7 +9594,8 @@ async function runAgentLoop(inputs) {
9503
9594
  ...usage !== void 0 ? { usage } : {},
9504
9595
  ...cost !== void 0 ? { cost } : {},
9505
9596
  ...ctx.error !== void 0 ? { error: ctx.error } : {},
9506
- ...ctx.stoppedAtIterationLimit === true ? { stoppedAtIterationLimit: true } : {}
9597
+ ...ctx.stoppedAtIterationLimit === true ? { stoppedAtIterationLimit: true } : {},
9598
+ ...ctx.stoppedByDoomLoop === true ? { stoppedByDoomLoop: true } : {}
9507
9599
  };
9508
9600
  } finally {
9509
9601
  if (ctxRef !== void 0 && ctxRef.memoryProviderHandle !== void 0 && inputs.memoryProvider !== void 0) {
@@ -9662,8 +9754,26 @@ async function continueOrTerminate(inputs, ctx, llmOutput) {
9662
9754
  }
9663
9755
  }
9664
9756
  pushToolConversationSteps(ctx, llmOutput.toolCalls, toolResults);
9757
+ if (await inspectDoomLoop(inputs, ctx, llmOutput.toolCalls) === "stop") return "done";
9665
9758
  return handleToolErrorContinuation(inputs, ctx, toolResults);
9666
9759
  }
9760
+ async function inspectDoomLoop(inputs, ctx, toolCalls) {
9761
+ if (ctx.doomLoop === void 0) return "continue";
9762
+ const verdict = firstDoomLoopVerdict(ctx.doomLoop, toolCalls);
9763
+ if (verdict.kind === "hard") {
9764
+ ctx.stoppedByDoomLoop = true;
9765
+ await emitAssistantTextStep(
9766
+ inputs,
9767
+ ctx,
9768
+ verdict.message ?? "Stopped: repeated identical tool calls made no progress."
9769
+ );
9770
+ return "stop";
9771
+ }
9772
+ if (verdict.kind === "soft") {
9773
+ ctx.messages.push({ role: "user", content: [{ type: "text", text: verdict.message ?? "" }] });
9774
+ }
9775
+ return "continue";
9776
+ }
9667
9777
 
9668
9778
  // src/internal/llm/fallback-client.ts
9669
9779
  init_errors();
@@ -12208,6 +12318,8 @@ function buildLoopInputs(options, runId, userText) {
12208
12318
  // M1-2: per-send iteration ceiling (validated above). The loop reads
12209
12319
  // inputs.maxIterations (default 8 when unset).
12210
12320
  ...maxIterations !== void 0 ? { maxIterations } : {},
12321
+ // Doom-loop guard config (default on; `false` disables, object tunes thresholds).
12322
+ ...options.sendOptions.doomLoop !== void 0 ? { doomLoop: options.sendOptions.doomLoop } : {},
12211
12323
  // D315-D317 — tool lifecycle hooks (cost tracking + audit + retry/alert)
12212
12324
  ...options.agentOptions.onToolStart !== void 0 ? { onToolStart: options.agentOptions.onToolStart } : {},
12213
12325
  ...options.agentOptions.onToolEnd !== void 0 ? { onToolEnd: options.agentOptions.onToolEnd } : {},
@@ -12340,6 +12452,7 @@ var RealLocalRun = class extends FixtureRunBase {
12340
12452
  if (output.usage !== void 0) this.script.usage = output.usage;
12341
12453
  if (output.cost !== void 0) this.script.cost = output.cost;
12342
12454
  if (output.stoppedAtIterationLimit === true) this.script.stoppedAtIterationLimit = true;
12455
+ if (output.stoppedByDoomLoop === true) this.script.stoppedByDoomLoop = true;
12343
12456
  if (output.error !== void 0 && this.script.errorDetail === void 0) {
12344
12457
  this.script.errorDetail = {
12345
12458
  message: output.error.message,