open-agents-ai 0.187.573 → 0.187.575

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/index.js CHANGED
@@ -252600,7 +252600,9 @@ async function probeService() {
252600
252600
  try {
252601
252601
  const controller = new AbortController();
252602
252602
  const timeout2 = setTimeout(() => controller.abort(), 3e3);
252603
- const res = await fetch(`${BASE_URL}/health`, { signal: controller.signal });
252603
+ const res = await fetch(`${BASE_URL}/health`, {
252604
+ signal: controller.signal
252605
+ });
252604
252606
  clearTimeout(timeout2);
252605
252607
  return res.ok;
252606
252608
  } catch {
@@ -252610,7 +252612,10 @@ async function probeService() {
252610
252612
  function findPython3() {
252611
252613
  for (const cmd of ["python3", "python"]) {
252612
252614
  try {
252613
- const ver = execSync19(`${cmd} --version 2>&1`, { stdio: "pipe", timeout: 5e3 }).toString().trim();
252615
+ const ver = execSync19(`${cmd} --version 2>&1`, {
252616
+ stdio: "pipe",
252617
+ timeout: 5e3
252618
+ }).toString().trim();
252614
252619
  if (ver.includes("Python 3"))
252615
252620
  return cmd;
252616
252621
  } catch {
@@ -252682,7 +252687,10 @@ async function ensureSession() {
252682
252687
  });
252683
252688
  const data = await res.json();
252684
252689
  if (!data.ok)
252685
- return { error: String(data.message ?? "Failed to start browser session"), sessionId: "" };
252690
+ return {
252691
+ error: String(data.message ?? "Failed to start browser session"),
252692
+ sessionId: ""
252693
+ };
252686
252694
  activeSessionId = data.session_id;
252687
252695
  return { sessionId: activeSessionId };
252688
252696
  }
@@ -252828,7 +252836,22 @@ var init_browser_action = __esm({
252828
252836
  properties: {
252829
252837
  action: {
252830
252838
  type: "string",
252831
- enum: ["navigate", "click", "click_xy", "type", "screenshot", "dom", "dom_summary", "vision_click", "scroll", "scroll_up", "scroll_down", "back", "forward", "close"],
252839
+ enum: [
252840
+ "navigate",
252841
+ "click",
252842
+ "click_xy",
252843
+ "type",
252844
+ "screenshot",
252845
+ "dom",
252846
+ "dom_summary",
252847
+ "vision_click",
252848
+ "scroll",
252849
+ "scroll_up",
252850
+ "scroll_down",
252851
+ "back",
252852
+ "forward",
252853
+ "close"
252854
+ ],
252832
252855
  description: "Browser action to perform. Key actions:\n- 'dom_summary': compact view of interactive elements (~1KB vs 200KB raw DOM)\n- 'vision_click': screenshot the page, use Moondream vision to find an element by description, then click it. Pass the element description in 'text' parameter (e.g. text='the login button'). This is the visual grounding loop from SeeAct.\n- 'click': click by CSS selector (fastest when you know the selector)\n- 'click_xy': click at pixel coordinates (when you have exact coords)"
252833
252856
  },
252834
252857
  url: {
@@ -252858,12 +252881,44 @@ var init_browser_action = __esm({
252858
252881
  },
252859
252882
  required: ["action"]
252860
252883
  };
252884
+ /** TASK-CLEANUP: gracefully close the browser session when the task completes. */
252885
+ async cleanup() {
252886
+ if (activeSessionId) {
252887
+ try {
252888
+ const res = await fetch(`${BASE_URL}/session/close`, {
252889
+ method: "POST",
252890
+ headers: { "Content-Type": "application/json" },
252891
+ body: JSON.stringify({ sid: activeSessionId }),
252892
+ signal: AbortSignal.timeout(5e3)
252893
+ });
252894
+ await res.json();
252895
+ } catch {
252896
+ }
252897
+ activeSessionId = null;
252898
+ }
252899
+ if (serviceProcess && serviceProcess.pid && !serviceProcess.killed) {
252900
+ try {
252901
+ process.kill(-serviceProcess.pid, "SIGKILL");
252902
+ } catch {
252903
+ }
252904
+ try {
252905
+ serviceProcess.kill("SIGKILL");
252906
+ } catch {
252907
+ }
252908
+ serviceProcess = null;
252909
+ }
252910
+ }
252861
252911
  async execute(args) {
252862
252912
  const start2 = Date.now();
252863
252913
  const action = args.action;
252864
252914
  const launchErr = await launchService();
252865
252915
  if (launchErr) {
252866
- return { success: false, output: "", error: launchErr, durationMs: Date.now() - start2 };
252916
+ return {
252917
+ success: false,
252918
+ output: "",
252919
+ error: launchErr,
252920
+ durationMs: Date.now() - start2
252921
+ };
252867
252922
  }
252868
252923
  if (action === "close") {
252869
252924
  if (activeSessionId) {
@@ -252873,21 +252928,41 @@ var init_browser_action = __esm({
252873
252928
  }
252874
252929
  activeSessionId = null;
252875
252930
  }
252876
- return { success: true, output: "Browser session closed.", durationMs: Date.now() - start2 };
252931
+ return {
252932
+ success: true,
252933
+ output: "Browser session closed.",
252934
+ durationMs: Date.now() - start2
252935
+ };
252877
252936
  }
252878
252937
  const session = await ensureSession();
252879
252938
  if (session.error) {
252880
- return { success: false, output: "", error: session.error, durationMs: Date.now() - start2 };
252939
+ return {
252940
+ success: false,
252941
+ output: "",
252942
+ error: session.error,
252943
+ durationMs: Date.now() - start2
252944
+ };
252881
252945
  }
252882
252946
  try {
252883
252947
  let result;
252884
252948
  switch (action) {
252885
252949
  case "navigate": {
252886
252950
  if (!args.url)
252887
- return { success: false, output: "", error: "url is required for navigate action", durationMs: Date.now() - start2 };
252888
- result = await apiCall("/navigate", "POST", { url: args.url });
252951
+ return {
252952
+ success: false,
252953
+ output: "",
252954
+ error: "url is required for navigate action",
252955
+ durationMs: Date.now() - start2
252956
+ };
252957
+ result = await apiCall("/navigate", "POST", {
252958
+ url: args.url
252959
+ });
252889
252960
  if (result.ok) {
252890
- return { success: true, output: `Navigated to ${args.url}`, durationMs: Date.now() - start2 };
252961
+ return {
252962
+ success: true,
252963
+ output: `Navigated to ${args.url}`,
252964
+ durationMs: Date.now() - start2
252965
+ };
252891
252966
  }
252892
252967
  const navMsg = String(result.message ?? "Navigation failed");
252893
252968
  const navHint = navMsg.toLowerCase().includes("connection") || navMsg.toLowerCase().includes("refused") || navMsg.toLowerCase().includes("err_connection") ? " (the URL appears unreachable — check if the target server is running and accepting connections)" : navMsg.toLowerCase().includes("timeout") ? " (page load timed out — try again or use a different URL)" : "";
@@ -252900,10 +252975,21 @@ var init_browser_action = __esm({
252900
252975
  }
252901
252976
  case "click": {
252902
252977
  if (!args.selector)
252903
- return { success: false, output: "", error: "selector is required for click action", durationMs: Date.now() - start2 };
252904
- result = await apiCall("/click", "POST", { selector: args.selector });
252978
+ return {
252979
+ success: false,
252980
+ output: "",
252981
+ error: "selector is required for click action",
252982
+ durationMs: Date.now() - start2
252983
+ };
252984
+ result = await apiCall("/click", "POST", {
252985
+ selector: args.selector
252986
+ });
252905
252987
  if (result.ok) {
252906
- return { success: true, output: `Clicked element: ${args.selector}`, durationMs: Date.now() - start2 };
252988
+ return {
252989
+ success: true,
252990
+ output: `Clicked element: ${args.selector}`,
252991
+ durationMs: Date.now() - start2
252992
+ };
252907
252993
  }
252908
252994
  const clickMsg = String(result.message ?? "Click failed");
252909
252995
  return {
@@ -252915,10 +253001,19 @@ var init_browser_action = __esm({
252915
253001
  }
252916
253002
  case "click_xy": {
252917
253003
  if (args.x == null || args.y == null)
252918
- return { success: false, output: "", error: "x and y are required for click_xy action", durationMs: Date.now() - start2 };
253004
+ return {
253005
+ success: false,
253006
+ output: "",
253007
+ error: "x and y are required for click_xy action",
253008
+ durationMs: Date.now() - start2
253009
+ };
252919
253010
  result = await apiCall("/click_xy", "POST", { x: args.x, y: args.y });
252920
253011
  if (result.ok) {
252921
- return { success: true, output: `Clicked at (${args.x}, ${args.y})`, durationMs: Date.now() - start2 };
253012
+ return {
253013
+ success: true,
253014
+ output: `Clicked at (${args.x}, ${args.y})`,
253015
+ durationMs: Date.now() - start2
253016
+ };
252922
253017
  }
252923
253018
  const xyMsg = String(result.message ?? "Click failed");
252924
253019
  return {
@@ -252930,10 +253025,22 @@ var init_browser_action = __esm({
252930
253025
  }
252931
253026
  case "type": {
252932
253027
  if (!args.selector || !args.text)
252933
- return { success: false, output: "", error: "selector and text are required for type action", durationMs: Date.now() - start2 };
252934
- result = await apiCall("/type", "POST", { selector: args.selector, text: args.text });
253028
+ return {
253029
+ success: false,
253030
+ output: "",
253031
+ error: "selector and text are required for type action",
253032
+ durationMs: Date.now() - start2
253033
+ };
253034
+ result = await apiCall("/type", "POST", {
253035
+ selector: args.selector,
253036
+ text: args.text
253037
+ });
252935
253038
  if (result.ok) {
252936
- return { success: true, output: `Typed "${args.text.slice(0, 50)}" into ${args.selector}`, durationMs: Date.now() - start2 };
253039
+ return {
253040
+ success: true,
253041
+ output: `Typed "${args.text.slice(0, 50)}" into ${args.selector}`,
253042
+ durationMs: Date.now() - start2
253043
+ };
252937
253044
  }
252938
253045
  const typeMsg = String(result.message ?? "Type failed");
252939
253046
  return {
@@ -252981,16 +253088,30 @@ var init_browser_action = __esm({
252981
253088
  durationMs: Date.now() - start2
252982
253089
  };
252983
253090
  }
252984
- return { success: false, output: "", error: "Screenshot failed", durationMs: Date.now() - start2 };
253091
+ return {
253092
+ success: false,
253093
+ output: "",
253094
+ error: "Screenshot failed",
253095
+ durationMs: Date.now() - start2
253096
+ };
252985
253097
  }
252986
253098
  case "dom": {
252987
253099
  result = await apiCall("/dom", "GET");
252988
253100
  const dom = result.dom;
252989
253101
  if (dom) {
252990
253102
  const truncated = dom.length > 5e4 ? dom.slice(0, 5e4) + "\n... (truncated)" : dom;
252991
- return { success: true, output: truncated, durationMs: Date.now() - start2 };
253103
+ return {
253104
+ success: true,
253105
+ output: truncated,
253106
+ durationMs: Date.now() - start2
253107
+ };
252992
253108
  }
252993
- return { success: false, output: "", error: "DOM capture failed", durationMs: Date.now() - start2 };
253109
+ return {
253110
+ success: false,
253111
+ output: "",
253112
+ error: "DOM capture failed",
253113
+ durationMs: Date.now() - start2
253114
+ };
252994
253115
  }
252995
253116
  // dom_summary: Research-grounded DOM downsampling
252996
253117
  // Paper: AgentOccam (arXiv:2410.13825, ICLR 2025) — pivotal node extraction
@@ -253004,9 +253125,18 @@ var init_browser_action = __esm({
253004
253125
  result = await apiCall("/dom", "GET");
253005
253126
  const rawDom = result.dom;
253006
253127
  if (!rawDom)
253007
- return { success: false, output: "", error: "DOM capture failed", durationMs: Date.now() - start2 };
253128
+ return {
253129
+ success: false,
253130
+ output: "",
253131
+ error: "DOM capture failed",
253132
+ durationMs: Date.now() - start2
253133
+ };
253008
253134
  const summary = downsampleDom(rawDom);
253009
- return { success: true, output: summary, durationMs: Date.now() - start2 };
253135
+ return {
253136
+ success: true,
253137
+ output: summary,
253138
+ durationMs: Date.now() - start2
253139
+ };
253010
253140
  }
253011
253141
  // vision_click: Screenshot → Moondream point detection → Click
253012
253142
  // Paper: SeeAct (arXiv:2401.01614) — visual grounding for web agents
@@ -253019,14 +253149,24 @@ var init_browser_action = __esm({
253019
253149
  case "vision_click": {
253020
253150
  const target = args.text;
253021
253151
  if (!target)
253022
- return { success: false, output: "", error: "text parameter is required for vision_click — describe what to click (e.g. 'the login button')", durationMs: Date.now() - start2 };
253152
+ return {
253153
+ success: false,
253154
+ output: "",
253155
+ error: "text parameter is required for vision_click — describe what to click (e.g. 'the login button')",
253156
+ durationMs: Date.now() - start2
253157
+ };
253023
253158
  const ssResult = await apiCall("/screenshot", "GET");
253024
253159
  const ssB64 = ssResult.b64;
253025
253160
  const ssWidth = ssResult.width || 1280;
253026
253161
  const ssHeight = ssResult.height || 720;
253027
253162
  const ssFile = ssResult.file;
253028
253163
  if (!ssB64 && !ssFile) {
253029
- return { success: false, output: "", error: "Screenshot failed — cannot perform vision click", durationMs: Date.now() - start2 };
253164
+ return {
253165
+ success: false,
253166
+ output: "",
253167
+ error: "Screenshot failed — cannot perform vision click",
253168
+ durationMs: Date.now() - start2
253169
+ };
253030
253170
  }
253031
253171
  let imagePath = "";
253032
253172
  if (ssFile) {
@@ -253038,7 +253178,12 @@ var init_browser_action = __esm({
253038
253178
  wfs(tmpPath, fileBuffer);
253039
253179
  imagePath = tmpPath;
253040
253180
  } catch (e2) {
253041
- return { success: false, output: "", error: `Failed to save screenshot: ${e2}`, durationMs: Date.now() - start2 };
253181
+ return {
253182
+ success: false,
253183
+ output: "",
253184
+ error: `Failed to save screenshot: ${e2}`,
253185
+ durationMs: Date.now() - start2
253186
+ };
253042
253187
  }
253043
253188
  } else if (ssB64) {
253044
253189
  const tmpPath = join41(process.env["TMPDIR"] || "/tmp", `oa-vision-click-${Date.now()}.png`);
@@ -253056,7 +253201,12 @@ var init_browser_action = __esm({
253056
253201
  prompt: target
253057
253202
  });
253058
253203
  if (!visionResult.success) {
253059
- return { success: false, output: `Vision could not find "${target}" on the page. Try using dom_summary to find the CSS selector instead.`, error: visionResult.error, durationMs: Date.now() - start2 };
253204
+ return {
253205
+ success: false,
253206
+ output: `Vision could not find "${target}" on the page. Try using dom_summary to find the CSS selector instead.`,
253207
+ error: visionResult.error,
253208
+ durationMs: Date.now() - start2
253209
+ };
253060
253210
  }
253061
253211
  const coordMatch = visionResult.output.match(/\((\d+\.?\d*),\s*(\d+\.?\d*)\)/);
253062
253212
  if (coordMatch) {
@@ -253066,10 +253216,19 @@ var init_browser_action = __esm({
253066
253216
  pointY = Math.round(normY * ssHeight);
253067
253217
  }
253068
253218
  } catch (e2) {
253069
- return { success: false, output: "", error: `Vision detection failed: ${e2}`, durationMs: Date.now() - start2 };
253219
+ return {
253220
+ success: false,
253221
+ output: "",
253222
+ error: `Vision detection failed: ${e2}`,
253223
+ durationMs: Date.now() - start2
253224
+ };
253070
253225
  }
253071
253226
  if (pointX < 0 || pointY < 0) {
253072
- return { success: false, output: `Could not determine click coordinates for "${target}". Vision returned no valid points.`, durationMs: Date.now() - start2 };
253227
+ return {
253228
+ success: false,
253229
+ output: `Could not determine click coordinates for "${target}". Vision returned no valid points.`,
253230
+ durationMs: Date.now() - start2
253231
+ };
253073
253232
  }
253074
253233
  const clickResult = await apiCall("/click_xy", "POST", {
253075
253234
  x: pointX,
@@ -253092,22 +253251,49 @@ var init_browser_action = __esm({
253092
253251
  };
253093
253252
  }
253094
253253
  case "scroll":
253095
- result = await apiCall("/scroll", "POST", { amount: args.amount ?? 600 });
253096
- return { success: !!result.ok, output: `Scrolled ${args.amount ?? 600}px`, durationMs: Date.now() - start2 };
253254
+ result = await apiCall("/scroll", "POST", {
253255
+ amount: args.amount ?? 600
253256
+ });
253257
+ return {
253258
+ success: !!result.ok,
253259
+ output: `Scrolled ${args.amount ?? 600}px`,
253260
+ durationMs: Date.now() - start2
253261
+ };
253097
253262
  case "scroll_up":
253098
253263
  result = await apiCall("/scroll/up", "POST");
253099
- return { success: !!result.ok, output: "Scrolled up", durationMs: Date.now() - start2 };
253264
+ return {
253265
+ success: !!result.ok,
253266
+ output: "Scrolled up",
253267
+ durationMs: Date.now() - start2
253268
+ };
253100
253269
  case "scroll_down":
253101
253270
  result = await apiCall("/scroll/down", "POST");
253102
- return { success: !!result.ok, output: "Scrolled down", durationMs: Date.now() - start2 };
253271
+ return {
253272
+ success: !!result.ok,
253273
+ output: "Scrolled down",
253274
+ durationMs: Date.now() - start2
253275
+ };
253103
253276
  case "back":
253104
253277
  result = await apiCall("/history/back", "POST");
253105
- return { success: !!result.ok, output: "Navigated back", durationMs: Date.now() - start2 };
253278
+ return {
253279
+ success: !!result.ok,
253280
+ output: "Navigated back",
253281
+ durationMs: Date.now() - start2
253282
+ };
253106
253283
  case "forward":
253107
253284
  result = await apiCall("/history/forward", "POST");
253108
- return { success: !!result.ok, output: "Navigated forward", durationMs: Date.now() - start2 };
253285
+ return {
253286
+ success: !!result.ok,
253287
+ output: "Navigated forward",
253288
+ durationMs: Date.now() - start2
253289
+ };
253109
253290
  default:
253110
- return { success: false, output: "", error: `Unknown action: ${action}. Available: navigate, click, click_xy, type, screenshot, dom, scroll, scroll_up, scroll_down, back, forward, close`, durationMs: Date.now() - start2 };
253291
+ return {
253292
+ success: false,
253293
+ output: "",
253294
+ error: `Unknown action: ${action}. Available: navigate, click, click_xy, type, screenshot, dom, scroll, scroll_up, scroll_down, back, forward, close`,
253295
+ durationMs: Date.now() - start2
253296
+ };
253111
253297
  }
253112
253298
  } catch (err) {
253113
253299
  return {
@@ -527925,6 +528111,10 @@ var init_agenticRunner = __esm({
527925
528111
  // can re-emit the same plan a second time (plan-replay) and execute
527926
528112
  // duplicate work because PROGRESS NUDGE alone is informational.
527927
528113
  _progressGateActive = false;
528114
+ // Consecutive gate blocks count. When the model ignores the gate and
528115
+ // retries a blocked tool, this counter increments. ≥2 triggers a system
528116
+ // message escalation to break pattern-lock loops.
528117
+ _consecutiveGateBlocks = 0;
527928
528118
  // REG-5: Rolling buffer of recent tool failures with their error output.
527929
528119
  // Surfaced before every LLM call so the agent can't ignore "I just ran this
527930
528120
  // and it errored". Detects same-fingerprint failure repetition and escalates
@@ -528174,15 +528364,19 @@ var init_agenticRunner = __esm({
528174
528364
  // DECOMP-2 (root-cause from batch531-midi-decomp, 2026-05-03): compelling
528175
528365
  // sub_agent delegation. DECOMP-1's informational directive was ignored
528176
528366
  // (0 sub_agent calls in 466 tool-call run despite directive at turn 1).
528177
- // Mirrors the BFC-61.G escalation arc: when the agent has edited
528178
- // ≥THRESHOLD distinct files in main context WITHOUT successful sub_agent,
528367
+ // Mirrors the BFC-61.G escalation arc, but must not deadlock delivery:
528368
+ // when the agent has edited adaptive-threshold distinct files in main context
528369
+ // WITHOUT successful sub_agent,
528179
528370
  // the dispatcher BLOCKS edits to NEW files (paths not yet edited) until
528180
528371
  // sub_agent succeeds. Edits to already-touched files are still allowed
528181
- // (current-module finishing work). Failed or malformed delegation does
528182
- // not clear the gate.
528372
+ // (current-module finishing work). Repeated failed delegation attempts
528373
+ // unlock a main-context fallback so the guardrail cannot become a hard
528374
+ // write-deadlock when sub_agent itself is broken or unavailable.
528183
528375
  // Kill switch: OA_DISABLE_DECOMP2=1.
528184
528376
  _decomp2MainContextFiles = /* @__PURE__ */ new Set();
528185
528377
  _decomp2SubAgentCalls = 0;
528378
+ _decomp2FailedDelegationCalls = 0;
528379
+ _decomp2FallbackAllowed = false;
528186
528380
  _decomp2GateActive = false;
528187
528381
  // MEM_PATH item #9: adaptive retrieval cache. When the (goalHash, recent-tool-sig)
528188
528382
  // hasn't changed since last retrieval, skip the PPR call entirely and reuse
@@ -528727,6 +528921,8 @@ Pick the SMALLEST concrete deliverable from the spec — typically the project e
528727
528921
  _maybeDecomp2Block(tc, turn) {
528728
528922
  if (!this._decomp2GateActive)
528729
528923
  return null;
528924
+ if (this._decomp2FallbackAllowed)
528925
+ return null;
528730
528926
  if (process.env["OA_DISABLE_DECOMP2"] === "1")
528731
528927
  return null;
528732
528928
  const _editTools = /* @__PURE__ */ new Set([
@@ -528747,7 +528943,7 @@ Pick the SMALLEST concrete deliverable from the spec — typically the project e
528747
528943
  const decomp2Msg = [
528748
528944
  `[BLOCKED — DECOMP-2 main-context exhaustion]`,
528749
528945
  ``,
528750
- `You have already edited ${this._decomp2MainContextFiles.size} distinct files in main context without invoking sub_agent. Continuing to edit ANOTHER new file ('${_editPath}') will keep your context window saturated and trigger compaction thrashing.`,
528946
+ `You have already edited ${this._decomp2MainContextFiles.size} distinct files in main context without a successful sub_agent. Continuing to edit another new file ('${_editPath}') may keep your context window saturated and trigger compaction thrashing.`,
528751
528947
  ``,
528752
528948
  `Files you've already edited (will accept further edits to these):`,
528753
528949
  _filesList,
@@ -528763,13 +528959,15 @@ Pick the SMALLEST concrete deliverable from the spec — typically the project e
528763
528959
  ` })`,
528764
528960
  ` 3. After sub_agent returns, mark the todo completed.`,
528765
528961
  ``,
528962
+ `If sub_agent keeps failing for reasons outside the module work, retry it once with corrected arguments. After repeated failed delegation attempts, OA will downgrade this from a hard block to an advisory fallback so file writes can continue.`,
528963
+ ``,
528766
528964
  `Why this matters: spreading edits across N files in main context burns ~N × file_size tokens. sub_agent gives the next module a focused context window.`,
528767
528965
  ``,
528768
528966
  `If you have ALREADY edited '${_editPath}' (this is a continuation), the orchestrator's set must have missed it — call file_read to verify, then re-edit. Otherwise, dispatch sub_agent now.`
528769
528967
  ].join("\n");
528770
528968
  this.emit({
528771
528969
  type: "status",
528772
- content: `DECOMP-2 NEW-FILE BLOCK — rejected ${tc.name}('${_editPath}') at turn ${turn}; gate stays active until sub_agent succeeds`,
528970
+ content: `DECOMP-2 NEW-FILE BLOCK — rejected ${tc.name}('${_editPath}') at turn ${turn}; gate stays active until sub_agent succeeds or repeated delegation failure unlocks fallback`,
528773
528971
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
528774
528972
  });
528775
528973
  this._tagSyntheticFailure({
@@ -528778,6 +528976,12 @@ Pick the SMALLEST concrete deliverable from the spec — typically the project e
528778
528976
  });
528779
528977
  return decomp2Msg;
528780
528978
  }
528979
+ _decomp2FileSpreadThreshold() {
528980
+ const ctx3 = this.options.contextWindowSize ?? 0;
528981
+ if (ctx3 <= 0)
528982
+ return 5;
528983
+ return Math.max(5, Math.min(30, Math.round(ctx3 / 6400)));
528984
+ }
528781
528985
  /**
528782
528986
  * DECOMP-2 post-dispatch tracking. Refactored from inline so both the
528783
528987
  * main turn loop AND the brute-force re-engagement inner loop record
@@ -528804,12 +529008,12 @@ Pick the SMALLEST concrete deliverable from the spec — typically the project e
528804
529008
  const _editPaths = this._extractToolTargetPaths(tc.name, tc.arguments, result);
528805
529009
  for (const _editPath of _editPaths) {
528806
529010
  this._decomp2MainContextFiles.add(_editPath);
528807
- const DECOMP2_FILE_SPREAD_THRESHOLD = 5;
528808
- if (!this._decomp2GateActive && this._decomp2MainContextFiles.size >= DECOMP2_FILE_SPREAD_THRESHOLD && this._decomp2SubAgentCalls === 0) {
529011
+ const DECOMP2_FILE_SPREAD_THRESHOLD = this._decomp2FileSpreadThreshold();
529012
+ if (!this._decomp2GateActive && !this._decomp2FallbackAllowed && this._decomp2MainContextFiles.size >= DECOMP2_FILE_SPREAD_THRESHOLD && this._decomp2SubAgentCalls === 0) {
528809
529013
  this._decomp2GateActive = true;
528810
529014
  this.emit({
528811
529015
  type: "status",
528812
- content: `DECOMP-2 NEW-FILE GATE ACTIVATED — ${this._decomp2MainContextFiles.size} distinct files edited in main context, 0 successful sub_agent calls; further edits to NEW files will be blocked until sub_agent succeeds`,
529016
+ content: `DECOMP-2 NEW-FILE GATE ACTIVATED — ${this._decomp2MainContextFiles.size} distinct files edited in main context, 0 successful sub_agent calls, threshold=${DECOMP2_FILE_SPREAD_THRESHOLD}; further edits to NEW files will be blocked until sub_agent succeeds or repeated delegation failure unlocks fallback`,
528813
529017
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
528814
529018
  });
528815
529019
  }
@@ -528818,15 +529022,27 @@ Pick the SMALLEST concrete deliverable from the spec — typically the project e
528818
529022
  if (tc.name === "sub_agent" || tc.name === "priority_delegate" || tc.name === "background_run") {
528819
529023
  if (result?.success !== true) {
528820
529024
  if (this._decomp2GateActive) {
529025
+ this._decomp2FailedDelegationCalls++;
528821
529026
  this.emit({
528822
529027
  type: "status",
528823
529028
  content: `DECOMP-2 DELEGATION FAILED — '${tc.name}' did not clear gate at turn ${turn}; fix delegation arguments/result before editing another new file`,
528824
529029
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
528825
529030
  });
529031
+ if (this._decomp2FailedDelegationCalls >= 2) {
529032
+ this._decomp2FallbackAllowed = true;
529033
+ this._decomp2GateActive = false;
529034
+ this.emit({
529035
+ type: "status",
529036
+ content: `DECOMP-2 FALLBACK UNLOCKED — ${this._decomp2FailedDelegationCalls} failed delegation attempts while gate was active; allowing main-context new-file edits so work can continue`,
529037
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
529038
+ });
529039
+ }
528826
529040
  }
528827
529041
  return;
528828
529042
  }
528829
529043
  this._decomp2SubAgentCalls++;
529044
+ this._decomp2FailedDelegationCalls = 0;
529045
+ this._decomp2FallbackAllowed = false;
528830
529046
  if (this._decomp2GateActive) {
528831
529047
  this._decomp2GateActive = false;
528832
529048
  this.emit({
@@ -530914,6 +531130,8 @@ Respond with your assessment, then take action.`;
530914
531130
  this._reg61PerpetualGateActive = false;
530915
531131
  this._decomp2MainContextFiles = /* @__PURE__ */ new Set();
530916
531132
  this._decomp2SubAgentCalls = 0;
531133
+ this._decomp2FailedDelegationCalls = 0;
531134
+ this._decomp2FallbackAllowed = false;
530917
531135
  this._decomp2GateActive = false;
530918
531136
  if (!globalThis.__oa_rca1_sigterm_installed) {
530919
531137
  globalThis.__oa_rca1_sigterm_installed = true;
@@ -533252,22 +533470,25 @@ ${memoryLines.join("\n")}`
533252
533470
  recentWrites.push({ path: path11, turn: info.lastWriteTurn ?? 0 });
533253
533471
  }
533254
533472
  }
533473
+ this._consecutiveGateBlocks++;
533255
533474
  recentWrites.sort((a2, b) => b.turn - a2.turn);
533256
533475
  const showWrites = recentWrites.slice(0, 16);
533476
+ const isRepeat = this._consecutiveGateBlocks >= 2;
533257
533477
  const gateMsg = [
533258
- `[PROGRESS GATE call todo_write FIRST before any other tool]`,
533478
+ `[BLOCKEDPROGRESS GATE active]`,
533259
533479
  ``,
533260
- `You have completed ${this._writesSinceLastTodoWrite} file modification${this._writesSinceLastTodoWrite === 1 ? "" : "s"} since your last todo_write call.`,
533261
- `The next tool call MUST be todo_write to mark progress. This is enforced — non-todo tool calls are intercepted until plan state is updated.`,
533480
+ `CAUSE: ${this._writesSinceLastTodoWrite} file writes since last todo_write call. Without progress tracking, the next turn re-plans the same work (plan-replay).`,
533481
+ `EFFECT: All non-todo tool calls are now blocked at the runtime level.`,
533482
+ `ACTION REQUIRED: Call todo_write with updated progress to release the gate.`,
533483
+ `CONSEQUENCE OF IGNORING: Retrying blocked tools does NOT work — only todo_write is accepted while the gate is active.`,
533484
+ isRepeat ? `
533485
+ [ESCALATION: This is block #${this._consecutiveGateBlocks}. You keep calling blocked tools instead of todo_write. The gate cannot be bypassed. You MUST call todo_write next.]` : "",
533262
533486
  ``,
533263
533487
  `Recent file modifications (use these to decide what's done):`,
533264
533488
  ...showWrites.map((w) => ` • ${w.path} (turn ${w.turn})`),
533265
533489
  recentWrites.length > showWrites.length ? ` • ... +${recentWrites.length - showWrites.length} more` : "",
533266
533490
  ``,
533267
- `Required action: call todo_write with the updated todo array — mark anything completed that these writes satisfy, advance the next item to in_progress, keep the rest pending.`,
533268
- `After todo_write succeeds, this gate releases and you can continue normal work.`,
533269
- ``,
533270
- `Why this exists: without the explicit progress update, your next turn will see the same in_progress todo, re-plan the same work, and re-emit identical tool calls (the "plan replay" failure mode that causes byte-identical writes to appear twice).`
533491
+ `Format: todo_write with todos array — mark items completed that these writes satisfy, advance next to in_progress. After todo_write succeeds, normal tools resume.`
533271
533492
  ].filter(Boolean).join("\n");
533272
533493
  this.emit({
533273
533494
  type: "tool_result",
@@ -533753,6 +533974,7 @@ Respond with EXACTLY this structure before your next tool call:
533753
533974
  }
533754
533975
  this._writesSinceLastTodoWrite = 0;
533755
533976
  this._progressGateActive = false;
533977
+ this._consecutiveGateBlocks = 0;
533756
533978
  }
533757
533979
  if (tc.name === "file_read") {
533758
533980
  const p2 = String(tc.arguments?.["path"] ?? tc.arguments?.["file"] ?? "");
@@ -534761,6 +534983,12 @@ Then use file_read on individual FILES inside it.`);
534761
534983
  const output = sr.result.success ? sr.result.output : `Error: ${sr.result.error || "unknown"}
534762
534984
  ${sr.result.output}`;
534763
534985
  messages2.push(this.buildToolMessage(output, matchTc.id, matchTc.name));
534986
+ if (this._consecutiveGateBlocks >= 2 && this._progressGateActive) {
534987
+ messages2.push({
534988
+ role: "system",
534989
+ content: `[PROGRESS GATE ESCALATION] You have made ${this._consecutiveGateBlocks} consecutive blocked tool calls without calling todo_write. The gate is enforced at the runtime level — retrying the same blocked tool will never work. Your NEXT call MUST be todo_write(todos=[...]) with updated progress. No other tool will be accepted until the gate is released.`
534990
+ });
534991
+ }
534764
534992
  if (matchTc.name === "task_complete") {
534765
534993
  const open2 = this.getOpenTodoItems();
534766
534994
  if (open2.length > 0) {
@@ -534801,6 +535029,12 @@ ${sr.result.output}`;
534801
535029
  const r2 = await executeSingle(tc);
534802
535030
  if (r2) {
534803
535031
  messages2.push(this.buildToolMessage(r2.output, r2.tc.id, r2.tc.name));
535032
+ if (this._consecutiveGateBlocks >= 2 && this._progressGateActive) {
535033
+ messages2.push({
535034
+ role: "system",
535035
+ content: `[PROGRESS GATE ESCALATION] You have made ${this._consecutiveGateBlocks} consecutive blocked tool calls without calling todo_write. The gate is enforced at the runtime level — retrying the same blocked tool will never work. Your NEXT call MUST be todo_write(todos=[...]) with updated progress. No other tool will be accepted until the gate is released.`
535036
+ });
535037
+ }
534804
535038
  if (r2.tc.name === "task_complete") {
534805
535039
  const open2 = this.getOpenTodoItems();
534806
535040
  if (open2.length > 0) {
@@ -534893,6 +535127,14 @@ ${sr.result.output}`;
534893
535127
  } else {
534894
535128
  completed = true;
534895
535129
  summary = extractTaskCompleteSummary(r2.tc.arguments);
535130
+ for (const tool of this.tools.values()) {
535131
+ if (tool.cleanup) {
535132
+ try {
535133
+ await tool.cleanup();
535134
+ } catch {
535135
+ }
535136
+ }
535137
+ }
534896
535138
  if (summary && !this._assistantTextEmitted) {
534897
535139
  this.emit({
534898
535140
  type: "assistant_text",
@@ -613853,7 +614095,9 @@ function adaptTool6(tool) {
613853
614095
  output: result.output,
613854
614096
  error: result.error
613855
614097
  };
613856
- }
614098
+ },
614099
+ // Pass through lifecycle hooks from the underlying Tool implementation
614100
+ cleanup: tool.cleanup
613857
614101
  };
613858
614102
  }
613859
614103
  function scanForSessionSignals(toolOutput) {
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.187.573",
3
+ "version": "0.187.575",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "open-agents-ai",
9
- "version": "0.187.573",
9
+ "version": "0.187.575",
10
10
  "hasInstallScript": true,
11
11
  "license": "CC-BY-NC-4.0",
12
12
  "dependencies": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.187.573",
3
+ "version": "0.187.575",
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",