open-agents-ai 0.12.4 → 0.12.5

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 (2) hide show
  1. package/dist/index.js +80 -6
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7421,10 +7421,49 @@ If you notice you're performing the SAME multi-step sequence for the 3rd time or
7421
7421
  }
7422
7422
  }
7423
7423
  }
7424
+ /**
7425
+ * Detect repetition in recent tool calls.
7426
+ * Returns a score 0-1 where 1 = fully repetitive (stuck in a loop).
7427
+ */
7428
+ detectRepetition(recentToolCalls) {
7429
+ if (recentToolCalls.length < 4)
7430
+ return 0;
7431
+ const window = recentToolCalls.slice(-8);
7432
+ const uniqueKeys = new Set(window.map((tc) => `${tc.name}:${tc.argsKey}`));
7433
+ const ratio = 1 - uniqueKeys.size / window.length;
7434
+ return ratio;
7435
+ }
7436
+ /**
7437
+ * Build a self-eval prompt for the agent when approaching timeout.
7438
+ * Returns the prompt to inject. The agent will respond with a plan.
7439
+ */
7440
+ buildTimeoutSelfEvalPrompt(elapsedMs2, toolCallCount, repetitionScore, remainingMs) {
7441
+ const elapsedMin = (elapsedMs2 / 6e4).toFixed(1);
7442
+ const remainingMin = (remainingMs / 6e4).toFixed(1);
7443
+ const stuckWarning = repetitionScore > 0.5 ? `
7444
+ \u26A0 REPETITION DETECTED: Your recent tool calls are ${Math.round(repetitionScore * 100)}% repetitive. You may be stuck in a loop.` : "";
7445
+ return `[TIMEOUT APPROACHING \u2014 Self-Assessment Required]
7446
+
7447
+ You have been working for ${elapsedMin} minutes with ${toolCallCount} tool calls. You have approximately ${remainingMin} minutes remaining.${stuckWarning}
7448
+
7449
+ ASSESS YOUR SITUATION and choose ONE action:
7450
+
7451
+ 1. CONTINUE \u2014 If you are making genuine progress on a long task, say "CONTINUE" and briefly explain what progress you've made and what remains. You will get an extended time window.
7452
+
7453
+ 2. PIVOT \u2014 If your current approach isn't working, say "PIVOT" and describe a completely different strategy. Then immediately try that new approach.
7454
+
7455
+ 3. CHECKPOINT \u2014 If you've made partial progress, say "CHECKPOINT" and call task_complete with a summary of what you accomplished so far. The user can continue from where you left off.
7456
+
7457
+ Respond with your assessment, then take action. Do NOT just say you'll continue without explaining concrete progress. Be honest about whether you're stuck.`;
7458
+ }
7424
7459
  /** Run a task through the agentic loop */
7425
7460
  async run(task, context) {
7426
7461
  const start = Date.now();
7427
- const deadline = start + this.options.taskTimeoutMs;
7462
+ const taskTimeoutMs = this.options.taskTimeoutMs;
7463
+ const softDeadline = start + Math.floor(taskTimeoutMs * 0.8);
7464
+ const hardDeadline = start + Math.floor(taskTimeoutMs * 1.5);
7465
+ let softTimeoutTriggered = false;
7466
+ const toolCallLog = [];
7428
7467
  this.aborted = false;
7429
7468
  this.pendingUserMessages.length = 0;
7430
7469
  const systemPrompt = this.options.dynamicContext ? `${SYSTEM_PROMPT}
@@ -7448,10 +7487,26 @@ TASK: ${task}` : task }
7448
7487
  this.emit({ type: "error", content: "Task aborted by user", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
7449
7488
  break;
7450
7489
  }
7451
- if (Date.now() > deadline) {
7452
- this.emit({ type: "error", content: "Task timeout reached", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
7490
+ const now = Date.now();
7491
+ if (now > hardDeadline) {
7492
+ this.emit({ type: "error", content: "Task hard timeout reached", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
7453
7493
  break;
7454
7494
  }
7495
+ if (!softTimeoutTriggered && now > softDeadline) {
7496
+ softTimeoutTriggered = true;
7497
+ const elapsed = now - start;
7498
+ const remaining = hardDeadline - now;
7499
+ const repetitionScore = this.detectRepetition(toolCallLog);
7500
+ this.emit({
7501
+ type: "compaction",
7502
+ content: `Timeout approaching (${(elapsed / 6e4).toFixed(1)}m elapsed) \u2014 injecting self-assessment`,
7503
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
7504
+ });
7505
+ messages.push({
7506
+ role: "user",
7507
+ content: this.buildTimeoutSelfEvalPrompt(elapsed, toolCallCount, repetitionScore, remaining)
7508
+ });
7509
+ }
7455
7510
  while (this.pendingUserMessages.length > 0) {
7456
7511
  const userMsg = this.pendingUserMessages.shift();
7457
7512
  const imagePattern = /\[IMAGE_BASE64:([^:]+):([^\]]+)\]/;
@@ -7516,6 +7571,8 @@ Integrate this guidance into your current approach. Continue working on the task
7516
7571
  if (this.aborted)
7517
7572
  break;
7518
7573
  toolCallCount++;
7574
+ const argsKey = Object.keys(tc.arguments ?? {}).sort().join(",");
7575
+ toolCallLog.push({ name: tc.name, argsKey });
7519
7576
  this.emit({
7520
7577
  type: "tool_call",
7521
7578
  toolName: tc.name,
@@ -7576,7 +7633,7 @@ ${result.output}`;
7576
7633
  });
7577
7634
  }
7578
7635
  }
7579
- if (!completed && !this.aborted && this.options.bruteForce && bruteForceCycle < this.options.bruteForceMaxCycles && Date.now() < deadline) {
7636
+ if (!completed && !this.aborted && this.options.bruteForce && bruteForceCycle < this.options.bruteForceMaxCycles && Date.now() < hardDeadline) {
7580
7637
  bruteForceCycle++;
7581
7638
  const totalTurns = messages.filter((m) => m.role === "assistant").length;
7582
7639
  this.emit({
@@ -7606,10 +7663,26 @@ You have ${this.options.maxTurns} more turns. Be creative and investigative. If
7606
7663
  this.emit({ type: "error", content: "Task aborted by user", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
7607
7664
  break;
7608
7665
  }
7609
- if (Date.now() > deadline) {
7610
- this.emit({ type: "error", content: "Task timeout reached", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
7666
+ const bfNow = Date.now();
7667
+ if (bfNow > hardDeadline) {
7668
+ this.emit({ type: "error", content: "Task hard timeout reached", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
7611
7669
  break;
7612
7670
  }
7671
+ if (!softTimeoutTriggered && bfNow > softDeadline) {
7672
+ softTimeoutTriggered = true;
7673
+ const elapsed = bfNow - start;
7674
+ const remaining = hardDeadline - bfNow;
7675
+ const repetitionScore = this.detectRepetition(toolCallLog);
7676
+ this.emit({
7677
+ type: "compaction",
7678
+ content: `Timeout approaching (${(elapsed / 6e4).toFixed(1)}m elapsed) \u2014 injecting self-assessment`,
7679
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
7680
+ });
7681
+ messages.push({
7682
+ role: "user",
7683
+ content: this.buildTimeoutSelfEvalPrompt(elapsed, toolCallCount, repetitionScore, remaining)
7684
+ });
7685
+ }
7613
7686
  while (this.pendingUserMessages.length > 0) {
7614
7687
  const userMsg = this.pendingUserMessages.shift();
7615
7688
  const imagePattern = /\[IMAGE_BASE64:([^:]+):([^\]]+)\]/;
@@ -7652,6 +7725,7 @@ Integrate this guidance into your current approach. Continue working on the task
7652
7725
  if (this.aborted)
7653
7726
  break;
7654
7727
  toolCallCount++;
7728
+ toolCallLog.push({ name: tc.name, argsKey: Object.keys(tc.arguments ?? {}).sort().join(",") });
7655
7729
  this.emit({ type: "tool_call", toolName: tc.name, toolArgs: tc.arguments, turn, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
7656
7730
  const tool = this.tools.get(tc.name);
7657
7731
  let result;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.12.4",
3
+ "version": "0.12.5",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) — interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",