open-agents-ai 0.187.461 → 0.187.462

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
@@ -254253,6 +254253,25 @@ var init_todo_write = __esm({
254253
254253
  }
254254
254254
  const sessionId = getTodoSessionId();
254255
254255
  const oldTodos = readTodos(sessionId);
254256
+ const canonicalize = (todos) => JSON.stringify(todos.map((t2) => ({
254257
+ content: t2.content,
254258
+ status: t2.status,
254259
+ parentId: t2.parentId ?? null,
254260
+ blocker: t2.blocker ?? null
254261
+ })));
254262
+ const oldKey = canonicalize(oldTodos);
254263
+ const newKey = canonicalize(incoming);
254264
+ if (oldKey === newKey && oldTodos.length > 0) {
254265
+ return {
254266
+ success: true,
254267
+ output: JSON.stringify({
254268
+ reminder: "[NO-OP] You called todo_write with the same plan you already have. The list is unchanged. Use todo_write ONLY to add new tasks, mark a task in_progress / completed, or record a blocker — not as a way to re-read your plan (use todo_read for that, but the current plan is also surfaced automatically in your context). Proceed with the current in_progress task.",
254269
+ todos: oldTodos,
254270
+ noop: true
254271
+ }),
254272
+ durationMs: performance.now() - start2
254273
+ };
254274
+ }
254256
254275
  const result = writeTodos(sessionId, incoming);
254257
254276
  const closedCount = result.newTodos.filter((t2) => t2.status === "completed").length;
254258
254277
  const oldClosedCount = oldTodos.filter((t2) => t2.status === "completed").length;
@@ -518395,6 +518414,115 @@ ${body}`;
518395
518414
  * Returns null when the disable knob is set or the backend is missing the
518396
518415
  * chatCompletion method.
518397
518416
  */
518417
+ /**
518418
+ * REG-3: Render the current todo list as a compact transient block so the
518419
+ * agent can read its own plan without calling todo_read or re-emitting
518420
+ * todo_write as a scratchpad. Returns null when there are no todos yet.
518421
+ */
518422
+ _renderTodoStateBlock(turn) {
518423
+ try {
518424
+ const { readTodos: readTodos2 } = __require("@open-agents/execution");
518425
+ const { getTodoSessionId: getTodoSessionId2 } = __require("@open-agents/execution");
518426
+ const sessionId = getTodoSessionId2();
518427
+ const todos = readTodos2(sessionId);
518428
+ if (!todos || todos.length === 0)
518429
+ return null;
518430
+ const lines = ["[CURRENT TODO PLAN — already in your state, no need to call todo_write to see it]"];
518431
+ const inProg = todos.filter((t2) => t2.status === "in_progress");
518432
+ const pending = todos.filter((t2) => t2.status === "pending");
518433
+ const done = todos.filter((t2) => t2.status === "completed");
518434
+ const blocked = todos.filter((t2) => t2.status === "blocked");
518435
+ if (inProg.length > 0) {
518436
+ lines.push(`▶ in_progress (${inProg.length}):`);
518437
+ for (const t2 of inProg)
518438
+ lines.push(` • ${t2.content}`);
518439
+ }
518440
+ if (pending.length > 0) {
518441
+ lines.push(`◯ pending (${pending.length}):`);
518442
+ for (const t2 of pending.slice(0, 8))
518443
+ lines.push(` • ${t2.content}`);
518444
+ if (pending.length > 8)
518445
+ lines.push(` … +${pending.length - 8} more`);
518446
+ }
518447
+ if (blocked.length > 0) {
518448
+ lines.push(`✕ blocked (${blocked.length}):`);
518449
+ for (const t2 of blocked)
518450
+ lines.push(` • ${t2.content}${t2.blocker ? ` — ${t2.blocker}` : ""}`);
518451
+ }
518452
+ if (done.length > 0) {
518453
+ lines.push(`✓ completed (${done.length}): ${done.map((t2) => t2.content.slice(0, 40)).join(" | ")}`);
518454
+ }
518455
+ lines.push(`(turn ${turn} — call todo_write ONLY to update state, never to re-read this plan)`);
518456
+ return lines.join("\n");
518457
+ } catch {
518458
+ return null;
518459
+ }
518460
+ }
518461
+ /**
518462
+ * REG-2: Render a compact "files known to me" block from `_worldFacts`.
518463
+ * Goal: tell the model what it has already touched this run so it stops
518464
+ * re-running `find src -type f` and `list_directory("...")` to confirm
518465
+ * its own outputs.
518466
+ *
518467
+ * Returns null when there is nothing useful to surface yet.
518468
+ */
518469
+ _renderFilesystemStateBlock(turn) {
518470
+ const wf = this._worldFacts;
518471
+ if (!wf)
518472
+ return null;
518473
+ const files = [...wf.files.entries()];
518474
+ const lists = [...wf.lastLists.entries()];
518475
+ if (files.length === 0 && lists.length === 0 && !wf.lastTest?.summary)
518476
+ return null;
518477
+ const writes = files.filter(([, v]) => (v.writeCount ?? 0) > 0);
518478
+ const reads = files.filter(([, v]) => (v.writeCount ?? 0) === 0);
518479
+ const groupByDir = (entries) => {
518480
+ const m2 = /* @__PURE__ */ new Map();
518481
+ for (const [p2] of entries) {
518482
+ const slash = p2.lastIndexOf("/");
518483
+ const dir = slash > 0 ? p2.slice(0, slash) : ".";
518484
+ const arr = m2.get(dir) ?? [];
518485
+ arr.push(p2);
518486
+ m2.set(dir, arr);
518487
+ }
518488
+ return m2;
518489
+ };
518490
+ const renderGroup = (m2, cap) => {
518491
+ const lines = [];
518492
+ const sorted = [...m2.entries()].sort((a2, b) => b[1].length - a2[1].length);
518493
+ let total = 0;
518494
+ for (const [dir, paths] of sorted) {
518495
+ if (total >= cap) {
518496
+ lines.push(` ... and ${sorted.slice(lines.length).reduce((s2, [, p2]) => s2 + p2.length, 0)} more files in other dirs`);
518497
+ break;
518498
+ }
518499
+ const show = paths.slice(0, 6);
518500
+ lines.push(` ${dir}/ → ${show.map((p2) => p2.slice(dir.length + 1)).join(", ")}${paths.length > show.length ? ` (+${paths.length - show.length} more)` : ""}`);
518501
+ total += paths.length;
518502
+ }
518503
+ return lines.join("\n");
518504
+ };
518505
+ const sections = [];
518506
+ sections.push("[FILES KNOWN — already touched this run; do not re-discover via find/list_directory unless filesystem may have changed externally]");
518507
+ if (writes.length > 0) {
518508
+ sections.push(`Files YOU WROTE this run (${writes.length}):`);
518509
+ sections.push(renderGroup(groupByDir(writes), 40));
518510
+ }
518511
+ if (reads.length > 0) {
518512
+ sections.push(`Files YOU READ this run (${reads.length}):`);
518513
+ sections.push(renderGroup(groupByDir(reads), 20));
518514
+ }
518515
+ if (lists.length > 0) {
518516
+ const recentLists = lists.sort((a2, b) => b[1].lastSeenTurn - a2[1].lastSeenTurn).slice(0, 8);
518517
+ sections.push(`Directories you've listed (${lists.length}):`);
518518
+ sections.push(recentLists.map(([dir, info]) => ` ${dir} (${info.entriesCount} entries, turn ${info.lastSeenTurn})`).join("\n"));
518519
+ }
518520
+ if (wf.lastTest?.summary) {
518521
+ sections.push(`Last test outcome: ${wf.lastTest.passed ? "PASSED" : "FAILED"} (turn ${wf.lastTest.turn ?? "?"})`);
518522
+ }
518523
+ sections.push(`(turn ${turn} — this block is regenerated every turn from your tool history)`);
518524
+ return sections.join("\n");
518525
+ }
518398
518526
  makePhaseSummarizer() {
518399
518527
  if (process.env["OA_DISABLE_PHASE_SUMMARIZER"] === "1")
518400
518528
  return null;
@@ -519095,7 +519223,7 @@ TASK: ${task}` : task;
519095
519223
  const recentToolResults = /* @__PURE__ */ new Map();
519096
519224
  const toolCallBudget = /* @__PURE__ */ new Map();
519097
519225
  const loopTier = this.options.modelTier ?? "large";
519098
- const toolBudgets = loopTier === "small" ? { web_search: 6, web_fetch: 4, list_directory: 8, find_files: 6, grep_search: 8 } : loopTier === "medium" ? { web_search: 10, web_fetch: 8, list_directory: 12, find_files: 10, grep_search: 12 } : { web_search: 20, web_fetch: 15, list_directory: 20, find_files: 15, grep_search: 20 };
519226
+ const toolBudgets = loopTier === "small" ? { web_search: 6, web_fetch: 4, list_directory: 12, find_files: 10, grep_search: 12 } : loopTier === "medium" ? { web_search: 10, web_fetch: 8, list_directory: 18, find_files: 14, grep_search: 18 } : { web_search: 20, web_fetch: 15, list_directory: 30, find_files: 20, grep_search: 30 };
519099
519227
  for (const [tool, budget] of Object.entries(toolBudgets)) {
519100
519228
  toolCallBudget.set(tool, budget);
519101
519229
  }
@@ -519462,6 +519590,22 @@ ${memoryLines.join("\n")}`
519462
519590
  }
519463
519591
  });
519464
519592
  }
519593
+ const _injections = [];
519594
+ const fsBlock = this._renderFilesystemStateBlock(turn);
519595
+ if (fsBlock)
519596
+ _injections.push(fsBlock);
519597
+ const todoBlock = this._renderTodoStateBlock(turn);
519598
+ if (todoBlock)
519599
+ _injections.push(todoBlock);
519600
+ if (_injections.length > 0) {
519601
+ const reqMsgs = chatRequest.messages;
519602
+ if (Array.isArray(reqMsgs)) {
519603
+ const insertAt = Math.max(0, reqMsgs.length - 1);
519604
+ for (const blk of _injections) {
519605
+ reqMsgs.splice(insertAt, 0, { role: "system", content: blk });
519606
+ }
519607
+ }
519608
+ }
519465
519609
  let response;
519466
519610
  try {
519467
519611
  response = this.options.streamEnabled && this.hasStreamingSupport() ? await this.streamingRequest(chatRequest, turn) : await this.backend.chatCompletion(chatRequest);
@@ -519741,14 +519885,56 @@ ${memoryLines.join("\n")}`
519741
519885
  content: `Phase contraction: ${contracted.join(", ")} → contracted (${summarizer ? "LLM-summarized" : "stub-summarized"}, anchors retained)`,
519742
519886
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
519743
519887
  });
519888
+ try {
519889
+ const archDir = __require("node:path").join(process.cwd(), ".oa", "phases");
519890
+ __require("node:fs").mkdirSync(archDir, { recursive: true });
519891
+ for (const phaseName of contracted) {
519892
+ const node = this._contextTree.getSnapshot().phases[phaseName];
519893
+ if (!node)
519894
+ continue;
519895
+ const stamp = `${phaseName}-${Date.now().toString(36)}-turn${turn}`;
519896
+ const archPath = __require("node:path").join(archDir, `${stamp}.jsonl`);
519897
+ const archived = {
519898
+ phase: phaseName,
519899
+ contractedAtTurn: turn,
519900
+ contractedAtIso: (/* @__PURE__ */ new Date()).toISOString(),
519901
+ anchors: node.anchors,
519902
+ summary: node.summary ?? null,
519903
+ messages: node.messages ?? []
519904
+ };
519905
+ __require("node:fs").writeFileSync(archPath, JSON.stringify(archived) + "\n", "utf-8");
519906
+ this._contextTree.archive(phaseName, archPath);
519907
+ }
519908
+ this.emit({
519909
+ type: "status",
519910
+ content: `Phase archive: ${contracted.length} phase(s) written to .oa/phases/`,
519911
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
519912
+ });
519913
+ } catch (archErr) {
519914
+ this.emit({
519915
+ type: "status",
519916
+ content: `Phase archive failed (non-fatal): ${archErr instanceof Error ? archErr.message : String(archErr)}`,
519917
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
519918
+ });
519919
+ }
519744
519920
  }
519745
519921
  this._phaseMessageStartIdx = messages2.length;
519746
519922
  this._taskState.phase = transition.to;
519747
519923
  this._taskState.phaseSince = turn;
519924
+ for (const [tool2, budget] of Object.entries(toolBudgets)) {
519925
+ toolCallBudget.set(tool2, budget);
519926
+ }
519927
+ this.emit({
519928
+ type: "status",
519929
+ content: `Tool budgets reset for new phase (${Object.keys(toolBudgets).length} tools)`,
519930
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
519931
+ });
519748
519932
  }
519749
519933
  }
519934
+ const _argsKeyForBudget = `${tc.name}:${argsKey}`;
519935
+ const _isCachedHit = recentToolResults.has(_argsKeyForBudget);
519750
519936
  const budgetRemaining = toolCallBudget.get(tc.name);
519751
- if (budgetRemaining !== void 0) {
519937
+ if (budgetRemaining !== void 0 && !_isCachedHit) {
519752
519938
  if (budgetRemaining <= 0) {
519753
519939
  this.emit({
519754
519940
  type: "tool_call",
@@ -519757,7 +519943,7 @@ ${memoryLines.join("\n")}`
519757
519943
  turn,
519758
519944
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
519759
519945
  });
519760
- const budgetMsg = `[BUDGET EXHAUSTED] You have used all ${toolBudgets[tc.name]} allowed ${tc.name} calls for this task. You ALREADY have enough information from previous calls. DO NOT try to call ${tc.name} again — it will be blocked. Summarize what you found and call task_complete with your answer NOW.`;
519946
+ const budgetMsg = `[BUDGET EXHAUSTED] You have used all ${toolBudgets[tc.name]} allowed ${tc.name} calls for the current phase. You ALREADY have enough information from previous calls. DO NOT try to call ${tc.name} again — it will be blocked. If your todo list shows more phases pending: mark the current phase completed via todo_write so a new budget allowance kicks in. If all phases are done: call task_complete with your final summary.`;
519761
519947
  this.emit({
519762
519948
  type: "tool_result",
519763
519949
  toolName: tc.name,
@@ -519939,10 +520125,33 @@ ${cachedEntry2.result.slice(0, 500)}` : `[BLOCKED — the observer confirmed thi
519939
520125
  updated.lastOutcomeTurn = turn;
519940
520126
  this._argCohorts.set(cohortKey, updated);
519941
520127
  try {
520128
+ if (tc.name === "file_write" || tc.name === "file_edit" || tc.name === "edit_block") {
520129
+ const p2 = String(tc.arguments?.["path"] ?? tc.arguments?.["file_path"] ?? tc.arguments?.["file"] ?? "");
520130
+ if (p2 && result.success) {
520131
+ const prev = this._worldFacts.files.get(p2);
520132
+ this._worldFacts.files.set(p2, {
520133
+ exists: true,
520134
+ size: prev?.size,
520135
+ hashSample: prev?.hashSample,
520136
+ lastSeenTurn: turn,
520137
+ lastWriteTurn: turn,
520138
+ writeCount: (prev?.writeCount ?? 0) + 1
520139
+ });
520140
+ }
520141
+ }
519942
520142
  if (tc.name === "file_read") {
519943
520143
  const p2 = String(tc.arguments?.["path"] ?? tc.arguments?.["file"] ?? "");
519944
- if (p2)
519945
- this._worldFacts.files.set(p2, { exists: result.success, size: (result.output || "").length, hashSample: (result.output || "").slice(0, 32), lastSeenTurn: turn });
520144
+ if (p2) {
520145
+ const prev = this._worldFacts.files.get(p2);
520146
+ this._worldFacts.files.set(p2, {
520147
+ exists: result.success,
520148
+ size: (result.output || "").length,
520149
+ hashSample: (result.output || "").slice(0, 32),
520150
+ lastSeenTurn: turn,
520151
+ lastWriteTurn: prev?.lastWriteTurn,
520152
+ writeCount: prev?.writeCount
520153
+ });
520154
+ }
519946
520155
  if (this._fileSummaryStore && result.success && result.output && result.output.length > 100) {
519947
520156
  try {
519948
520157
  const existing = this._fileSummaryStore.get(p2);
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.187.461",
3
+ "version": "0.187.462",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "open-agents-ai",
9
- "version": "0.187.461",
9
+ "version": "0.187.462",
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.461",
3
+ "version": "0.187.462",
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",