@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/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();
@@ -11440,6 +11556,8 @@ function buildLoopInputs(options, runId, userText) {
11440
11556
  // M1-2: per-send iteration ceiling (validated above). The loop reads
11441
11557
  // inputs.maxIterations (default 8 when unset).
11442
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 } : {},
11443
11561
  // D315-D317 — tool lifecycle hooks (cost tracking + audit + retry/alert)
11444
11562
  ...options.agentOptions.onToolStart !== void 0 ? { onToolStart: options.agentOptions.onToolStart } : {},
11445
11563
  ...options.agentOptions.onToolEnd !== void 0 ? { onToolEnd: options.agentOptions.onToolEnd } : {},
@@ -11588,6 +11706,7 @@ var init_real_local_run = __esm({
11588
11706
  if (output.usage !== void 0) this.script.usage = output.usage;
11589
11707
  if (output.cost !== void 0) this.script.cost = output.cost;
11590
11708
  if (output.stoppedAtIterationLimit === true) this.script.stoppedAtIterationLimit = true;
11709
+ if (output.stoppedByDoomLoop === true) this.script.stoppedByDoomLoop = true;
11591
11710
  if (output.error !== void 0 && this.script.errorDetail === void 0) {
11592
11711
  this.script.errorDetail = {
11593
11712
  message: output.error.message,
@@ -14579,6 +14698,7 @@ function isEmptyRound(result) {
14579
14698
  return (result.result ?? "").trim() === "";
14580
14699
  }
14581
14700
  function classifyRound(result, round, maxRounds, emptyStreak) {
14701
+ if (result.stoppedByDoomLoop === true) return "no_progress";
14582
14702
  if (result.stoppedAtIterationLimit !== true) return "done";
14583
14703
  if (isEmptyRound(result) && emptyStreak >= 1) return "no_progress";
14584
14704
  if (round >= maxRounds) return "step_limit";