open-agents-ai 0.31.3 → 0.31.4

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 +393 -9
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -10990,6 +10990,7 @@ Rules:
10990
10990
  _pauseResolve = null;
10991
10991
  _sudoPassword = null;
10992
10992
  _sudoResolve = null;
10993
+ _pendingCompaction = null;
10993
10994
  constructor(backend, options) {
10994
10995
  this.backend = backend;
10995
10996
  this.options = {
@@ -11099,6 +11100,23 @@ Rules:
11099
11100
  get isPaused() {
11100
11101
  return this._paused;
11101
11102
  }
11103
+ /**
11104
+ * Request manual context compaction at the next turn boundary.
11105
+ * The strategy controls how aggressively context is compressed:
11106
+ * - "default" — standard progressive summarization
11107
+ * - "aggressive" — maximum compression, keep only decisions + errors
11108
+ * - "decisions" — extract action→outcome pairs, discard exploration
11109
+ * - "errors" — preserve error context, compress successes
11110
+ * - "summary" — high-level paragraph, minimal detail
11111
+ */
11112
+ requestCompaction(strategy = "default") {
11113
+ this._pendingCompaction = strategy;
11114
+ this.emit({
11115
+ type: "compaction",
11116
+ content: `Manual compaction requested (strategy: ${strategy})`,
11117
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
11118
+ });
11119
+ }
11102
11120
  /**
11103
11121
  * If paused, block until resume() or abort() is called.
11104
11122
  * Returns true if the loop should continue, false if aborted while paused.
@@ -11246,7 +11264,14 @@ Integrate this guidance into your current approach. Continue working on the task
11246
11264
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
11247
11265
  });
11248
11266
  }
11249
- const compacted = this.compactMessages(messages);
11267
+ let compacted;
11268
+ if (this._pendingCompaction) {
11269
+ const strategy = this._pendingCompaction;
11270
+ this._pendingCompaction = null;
11271
+ compacted = this.compactMessages(messages, strategy, true);
11272
+ } else {
11273
+ compacted = this.compactMessages(messages);
11274
+ }
11250
11275
  const { maxOutputTokens: effectiveMaxTokens } = this.contextLimits();
11251
11276
  const chatRequest = {
11252
11277
  messages: compacted,
@@ -11499,7 +11524,14 @@ Integrate this guidance into your current approach. Continue working on the task
11499
11524
  }
11500
11525
  this.emit({ type: "user_interrupt", content: userMsg.replace(/\[IMAGE_BASE64:[^\]]+\]/, "[image]").slice(0, 200), turn, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
11501
11526
  }
11502
- const compactedMsgs = this.compactMessages(messages);
11527
+ let compactedMsgs;
11528
+ if (this._pendingCompaction) {
11529
+ const strategy = this._pendingCompaction;
11530
+ this._pendingCompaction = null;
11531
+ compactedMsgs = this.compactMessages(messages, strategy, true);
11532
+ } else {
11533
+ compactedMsgs = this.compactMessages(messages);
11534
+ }
11503
11535
  const chatRequest = { messages: compactedMsgs, tools: toolDefs, temperature: this.options.temperature, maxTokens: this.options.maxTokens, timeoutMs: this.options.requestTimeoutMs };
11504
11536
  let response;
11505
11537
  try {
@@ -11683,7 +11715,7 @@ ${tail}`;
11683
11715
  // -------------------------------------------------------------------------
11684
11716
  // Context compaction
11685
11717
  // -------------------------------------------------------------------------
11686
- compactMessages(messages) {
11718
+ compactMessages(messages, strategy = "default", force = false) {
11687
11719
  if (messages.length < 3)
11688
11720
  return messages;
11689
11721
  const totalChars = messages.reduce((sum, m) => {
@@ -11696,10 +11728,15 @@ ${tail}`;
11696
11728
  }, 0);
11697
11729
  const estimatedTokens = totalChars / 4;
11698
11730
  const limits = this.contextLimits();
11699
- if (estimatedTokens < limits.compactionThreshold) {
11731
+ if (!force && estimatedTokens < limits.compactionThreshold) {
11700
11732
  return messages;
11701
11733
  }
11702
- const keepRecent = limits.keepRecent;
11734
+ if (force && messages.length < 5)
11735
+ return messages;
11736
+ let keepRecent = limits.keepRecent;
11737
+ if (strategy === "aggressive" || strategy === "summary") {
11738
+ keepRecent = Math.max(2, Math.floor(keepRecent / 2));
11739
+ }
11703
11740
  const head = messages.slice(0, 2);
11704
11741
  if (messages.length <= 2 + keepRecent)
11705
11742
  return messages;
@@ -11728,16 +11765,35 @@ ${tail}`;
11728
11765
  nonCompactionMiddle.push(msg);
11729
11766
  }
11730
11767
  }
11731
- const newSummary = this.summarizeCompactedMessages(nonCompactionMiddle);
11768
+ const maskedMiddle = this.maskOldObservations(nonCompactionMiddle);
11769
+ let newSummary;
11770
+ switch (strategy) {
11771
+ case "aggressive":
11772
+ newSummary = this.summarizeAggressive(maskedMiddle);
11773
+ break;
11774
+ case "decisions":
11775
+ newSummary = this.summarizeDecisions(maskedMiddle);
11776
+ break;
11777
+ case "errors":
11778
+ newSummary = this.summarizeErrors(maskedMiddle);
11779
+ break;
11780
+ case "summary":
11781
+ newSummary = this.summarizeHighLevel(maskedMiddle);
11782
+ break;
11783
+ default:
11784
+ newSummary = this.summarizeCompactedMessages(maskedMiddle);
11785
+ }
11732
11786
  const combinedSummary = previousSummary ? this.progressiveSummarize(previousSummary, newSummary) : newSummary;
11787
+ const strategyLabel = strategy !== "default" ? ` (${strategy})` : "";
11788
+ const forceLabel = force ? " [manual]" : "";
11733
11789
  this.emit({
11734
11790
  type: "compaction",
11735
- content: `Compacted ${middle.length} messages${previousSummary ? " (progressive)" : ""}`,
11791
+ content: `Compacted ${middle.length} messages${strategyLabel}${forceLabel}${previousSummary ? " (progressive)" : ""}`,
11736
11792
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
11737
11793
  });
11738
11794
  const compactionMsg = {
11739
11795
  role: "system",
11740
- content: `[Context compacted \u2014 summary of earlier work]
11796
+ content: `[Context compacted${strategyLabel} \u2014 summary of earlier work]
11741
11797
 
11742
11798
  ${combinedSummary}
11743
11799
 
@@ -11803,6 +11859,56 @@ ${newerSummary}` : newerSummary;
11803
11859
  }
11804
11860
  return condensed.join("\n");
11805
11861
  }
11862
+ /**
11863
+ * Observation masking pre-pass: replace verbose tool outputs with compact
11864
+ * placeholders. Errors and short outputs (<500 chars) are kept verbatim.
11865
+ * Inspired by OpenHands' ObservationMaskingCondenser — zero LLM cost.
11866
+ */
11867
+ maskOldObservations(messages) {
11868
+ const toolCallNames = /* @__PURE__ */ new Map();
11869
+ for (const msg of messages) {
11870
+ if (msg.tool_calls) {
11871
+ for (const tc of msg.tool_calls) {
11872
+ toolCallNames.set(tc.id, tc.function.name);
11873
+ }
11874
+ }
11875
+ }
11876
+ return messages.map((msg) => {
11877
+ if (msg.role !== "tool" || typeof msg.content !== "string")
11878
+ return msg;
11879
+ const content = msg.content;
11880
+ if (content.startsWith("Error:") || /^(FAIL|ERR!|TypeError|SyntaxError)/i.test(content))
11881
+ return msg;
11882
+ if (content.length < 500)
11883
+ return msg;
11884
+ const toolName = msg.tool_call_id ? toolCallNames.get(msg.tool_call_id) : void 0;
11885
+ const lines = content.split("\n").length;
11886
+ switch (toolName) {
11887
+ case "file_read":
11888
+ return { ...msg, content: `[file content: ${lines} lines, ${content.length} chars \u2014 omitted for compaction]` };
11889
+ case "grep_search":
11890
+ return { ...msg, content: `[grep results: ${lines} matches, ${content.length} chars \u2014 omitted for compaction]` };
11891
+ case "find_files":
11892
+ return { ...msg, content: `[find results: ${lines} files, omitted for compaction]` };
11893
+ case "list_directory":
11894
+ return { ...msg, content: `[directory listing: ${lines} entries, omitted for compaction]` };
11895
+ case "web_fetch":
11896
+ return { ...msg, content: `[web page content: ${content.length} chars \u2014 omitted for compaction]` };
11897
+ case "web_search":
11898
+ return { ...msg, content: `[search results: ${lines} results \u2014 omitted for compaction]` };
11899
+ case "shell":
11900
+ case "background_run":
11901
+ if (/PASS|FAIL|error|warning/i.test(content))
11902
+ return msg;
11903
+ return { ...msg, content: `[command output: ${lines} lines, ${content.length} chars \u2014 omitted for compaction]` };
11904
+ default:
11905
+ if (content.length > 2e3) {
11906
+ return { ...msg, content: `[${toolName ?? "tool"} output: ${content.length} chars \u2014 omitted for compaction]` };
11907
+ }
11908
+ return msg;
11909
+ }
11910
+ });
11911
+ }
11806
11912
  /**
11807
11913
  * Extract a rich structured summary from compacted messages, preserving:
11808
11914
  * - Assistant analysis/reasoning text
@@ -11970,6 +12076,253 @@ ${newerSummary}` : newerSummary;
11970
12076
  return parts.join("\n");
11971
12077
  }
11972
12078
  // -------------------------------------------------------------------------
12079
+ // Strategy-specific summarizers for manual /compact command
12080
+ // -------------------------------------------------------------------------
12081
+ /**
12082
+ * Aggressive compaction: extract only key decisions and errors.
12083
+ * Discards exploration, file reads, and search results entirely.
12084
+ * Keeps: what files were changed, what errors occurred, what approach was chosen.
12085
+ */
12086
+ summarizeAggressive(messages) {
12087
+ const decisions = [];
12088
+ const errors = [];
12089
+ const filesChanged = /* @__PURE__ */ new Map();
12090
+ let toolCallCount = 0;
12091
+ const toolCallMap = /* @__PURE__ */ new Map();
12092
+ for (const msg of messages) {
12093
+ if (msg.tool_calls) {
12094
+ for (const tc of msg.tool_calls) {
12095
+ toolCallCount++;
12096
+ const args = (() => {
12097
+ try {
12098
+ return JSON.parse(tc.function.arguments);
12099
+ } catch {
12100
+ return {};
12101
+ }
12102
+ })();
12103
+ toolCallMap.set(tc.id, { name: tc.function.name, args });
12104
+ const name = tc.function.name;
12105
+ if (name === "file_edit" || name === "file_write" || name === "batch_edit") {
12106
+ const path = String(args.path || "");
12107
+ if (path)
12108
+ filesChanged.set(path, name === "file_write" ? "created/rewritten" : "edited");
12109
+ }
12110
+ }
12111
+ }
12112
+ if (msg.role === "assistant" && typeof msg.content === "string" && msg.content.trim().length > 30) {
12113
+ const text = msg.content.trim();
12114
+ const decisionPatterns = /(?:I (?:will|need to|should|decided|chose|'ll)|Let me|The (?:fix|solution|approach|issue))[^.!?\n]{10,120}[.!?]/gi;
12115
+ const matches = text.match(decisionPatterns);
12116
+ if (matches) {
12117
+ for (const m of matches.slice(0, 2))
12118
+ decisions.push(m.trim());
12119
+ }
12120
+ }
12121
+ if (msg.role === "tool" && typeof msg.content === "string") {
12122
+ const tc = msg.tool_call_id ? toolCallMap.get(msg.tool_call_id) : void 0;
12123
+ if (msg.content.startsWith("Error:") || /FAIL|ERR!/i.test(msg.content)) {
12124
+ const ctx = tc ? `${tc.name}` : "tool";
12125
+ errors.push(`${ctx}: ${msg.content.split("\n")[0]?.slice(0, 150)}`);
12126
+ }
12127
+ }
12128
+ }
12129
+ const parts = [];
12130
+ parts.push(`## Aggressive Compact (${toolCallCount} tool calls compressed)
12131
+ `);
12132
+ if (filesChanged.size > 0) {
12133
+ parts.push("### Files Modified");
12134
+ for (const [path, action] of filesChanged)
12135
+ parts.push(`- \`${path}\` (${action})`);
12136
+ parts.push("");
12137
+ }
12138
+ if (decisions.length > 0) {
12139
+ parts.push("### Key Decisions");
12140
+ for (const d of decisions.slice(0, 5))
12141
+ parts.push(`- ${d}`);
12142
+ parts.push("");
12143
+ }
12144
+ if (errors.length > 0) {
12145
+ parts.push("### Errors (avoid repeating)");
12146
+ for (const e of errors.slice(0, 5))
12147
+ parts.push(`- ${e}`);
12148
+ }
12149
+ return parts.join("\n");
12150
+ }
12151
+ /**
12152
+ * Decisions compaction: extract action→outcome pairs only.
12153
+ * Each tool call becomes a single "did X → got Y" line.
12154
+ */
12155
+ summarizeDecisions(messages) {
12156
+ const actionOutcomes = [];
12157
+ const toolCallMap = /* @__PURE__ */ new Map();
12158
+ for (const msg of messages) {
12159
+ if (msg.tool_calls) {
12160
+ for (const tc of msg.tool_calls) {
12161
+ const args = (() => {
12162
+ try {
12163
+ return JSON.parse(tc.function.arguments);
12164
+ } catch {
12165
+ return {};
12166
+ }
12167
+ })();
12168
+ toolCallMap.set(tc.id, { name: tc.function.name, args });
12169
+ }
12170
+ }
12171
+ if (msg.role === "tool" && typeof msg.content === "string" && msg.tool_call_id) {
12172
+ const tc = toolCallMap.get(msg.tool_call_id);
12173
+ if (!tc)
12174
+ continue;
12175
+ const content = msg.content;
12176
+ let action;
12177
+ let outcome;
12178
+ switch (tc.name) {
12179
+ case "file_read":
12180
+ action = `Read \`${tc.args.path}\``;
12181
+ outcome = `${content.split("\n").length} lines`;
12182
+ break;
12183
+ case "file_write":
12184
+ action = `Wrote \`${tc.args.path}\``;
12185
+ outcome = content.startsWith("Error:") ? content.slice(0, 100) : "success";
12186
+ break;
12187
+ case "file_edit":
12188
+ action = `Edited \`${tc.args.path}\``;
12189
+ outcome = content.startsWith("Error:") ? content.slice(0, 100) : "applied";
12190
+ break;
12191
+ case "shell":
12192
+ case "background_run":
12193
+ action = `Ran \`${String(tc.args.command || "").slice(0, 60)}\``;
12194
+ outcome = content.startsWith("Error:") ? "failed" : /PASS|success/i.test(content) ? "passed" : "completed";
12195
+ break;
12196
+ case "grep_search":
12197
+ action = `Searched "${tc.args.pattern}"`;
12198
+ outcome = `${(content.match(/\n/g) || []).length} matches`;
12199
+ break;
12200
+ default:
12201
+ action = tc.name;
12202
+ outcome = content.startsWith("Error:") ? "failed" : "ok";
12203
+ }
12204
+ actionOutcomes.push(`${action} \u2192 ${outcome}`);
12205
+ }
12206
+ }
12207
+ const parts = [];
12208
+ parts.push(`## Decision Log (${actionOutcomes.length} actions)
12209
+ `);
12210
+ for (const ao of actionOutcomes)
12211
+ parts.push(`- ${ao}`);
12212
+ return parts.join("\n");
12213
+ }
12214
+ /**
12215
+ * Error-preserving compaction: keep full error context for failure avoidance,
12216
+ * aggressively compress successful operations to single lines.
12217
+ */
12218
+ summarizeErrors(messages) {
12219
+ const errors = [];
12220
+ const successes = [];
12221
+ const toolCallMap = /* @__PURE__ */ new Map();
12222
+ for (const msg of messages) {
12223
+ if (msg.tool_calls) {
12224
+ for (const tc of msg.tool_calls) {
12225
+ const args = (() => {
12226
+ try {
12227
+ return JSON.parse(tc.function.arguments);
12228
+ } catch {
12229
+ return {};
12230
+ }
12231
+ })();
12232
+ toolCallMap.set(tc.id, { name: tc.function.name, args });
12233
+ }
12234
+ }
12235
+ if (msg.role === "tool" && typeof msg.content === "string" && msg.tool_call_id) {
12236
+ const tc = toolCallMap.get(msg.tool_call_id);
12237
+ if (!tc)
12238
+ continue;
12239
+ const content = msg.content;
12240
+ const isError = content.startsWith("Error:") || /FAIL|ERR!|error:|TypeError|SyntaxError/i.test(content);
12241
+ const action = tc.name === "shell" ? `\`${String(tc.args.command || "").slice(0, 80)}\`` : `${tc.name}(${String(tc.args.path || tc.args.pattern || "").slice(0, 60)})`;
12242
+ if (isError) {
12243
+ errors.push({
12244
+ context: action,
12245
+ error: content.slice(0, 300)
12246
+ });
12247
+ } else {
12248
+ successes.push(`${action} \u2192 ok`);
12249
+ }
12250
+ }
12251
+ }
12252
+ const parts = [];
12253
+ parts.push(`## Error-Focused Compact
12254
+ `);
12255
+ if (errors.length > 0) {
12256
+ parts.push("### Errors & Failures (preserve to avoid repeating)");
12257
+ for (const { context, error } of errors.slice(0, 8)) {
12258
+ parts.push(`- **${context}**:`);
12259
+ parts.push(` \`\`\`
12260
+ ${error}
12261
+ \`\`\``);
12262
+ }
12263
+ parts.push("");
12264
+ }
12265
+ if (successes.length > 0) {
12266
+ parts.push(`### Successful Operations (${successes.length} total)`);
12267
+ for (const s of successes.slice(0, 10))
12268
+ parts.push(`- ${s}`);
12269
+ if (successes.length > 10)
12270
+ parts.push(`- ... and ${successes.length - 10} more`);
12271
+ }
12272
+ return parts.join("\n");
12273
+ }
12274
+ /**
12275
+ * High-level summary: paragraph-form description of what was accomplished.
12276
+ * Minimal detail, maximum compression — good for very long sessions.
12277
+ */
12278
+ summarizeHighLevel(messages) {
12279
+ const filesModified = /* @__PURE__ */ new Set();
12280
+ const filesRead = /* @__PURE__ */ new Set();
12281
+ let shellCount = 0;
12282
+ let toolCallCount = 0;
12283
+ let lastAssistantText = "";
12284
+ for (const msg of messages) {
12285
+ if (msg.tool_calls) {
12286
+ for (const tc of msg.tool_calls) {
12287
+ toolCallCount++;
12288
+ const args = (() => {
12289
+ try {
12290
+ return JSON.parse(tc.function.arguments);
12291
+ } catch {
12292
+ return {};
12293
+ }
12294
+ })();
12295
+ const name = tc.function.name;
12296
+ if (name === "file_edit" || name === "file_write" || name === "batch_edit") {
12297
+ filesModified.add(String(args.path || ""));
12298
+ } else if (name === "file_read") {
12299
+ filesRead.add(String(args.path || ""));
12300
+ } else if (name === "shell" || name === "background_run") {
12301
+ shellCount++;
12302
+ }
12303
+ }
12304
+ }
12305
+ if (msg.role === "assistant" && typeof msg.content === "string" && msg.content.trim().length > 20) {
12306
+ lastAssistantText = msg.content.trim();
12307
+ }
12308
+ }
12309
+ const modList = Array.from(filesModified).filter(Boolean);
12310
+ const readList = Array.from(filesRead).filter(Boolean);
12311
+ const parts = [];
12312
+ parts.push(`## Session Summary (${toolCallCount} tool calls)
12313
+ `);
12314
+ parts.push(`Modified ${modList.length} file(s), read ${readList.length} file(s), ran ${shellCount} command(s).
12315
+ `);
12316
+ if (modList.length > 0) {
12317
+ parts.push(`Files changed: ${modList.slice(0, 8).map((f) => `\`${f}\``).join(", ")}${modList.length > 8 ? ` +${modList.length - 8} more` : ""}`);
12318
+ }
12319
+ if (lastAssistantText) {
12320
+ parts.push(`
12321
+ Last analysis: ${lastAssistantText.slice(0, 300)}${lastAssistantText.length > 300 ? "..." : ""}`);
12322
+ }
12323
+ return parts.join("\n");
12324
+ }
12325
+ // -------------------------------------------------------------------------
11973
12326
  // Tool definition builder
11974
12327
  // -------------------------------------------------------------------------
11975
12328
  buildToolDefinitions() {
@@ -14088,6 +14441,8 @@ function renderSlashHelp() {
14088
14441
  ["/stats", "Show session dashboard (metrics, tool usage, task history)"],
14089
14442
  ["/stop", "Stop current task and save progress (alias: /pause)"],
14090
14443
  ["/resume", "Resume a previously stopped task"],
14444
+ ["/compact", "Force context compaction now (default strategy)"],
14445
+ ["/compact <strategy>", "Compact with strategy: aggressive, decisions, errors, summary"],
14091
14446
  ["/bruteforce", "Toggle brute-force mode (auto re-engage on turn limit)"],
14092
14447
  ["/tools", "List agent-created custom tools"],
14093
14448
  ["/skills", "List available AIWG skills"],
@@ -14457,6 +14812,7 @@ var init_render = __esm({
14457
14812
  "/verbose",
14458
14813
  "/dream",
14459
14814
  "/bruteforce",
14815
+ "/compact",
14460
14816
  "/tools",
14461
14817
  "/skills",
14462
14818
  "/clear",
@@ -16578,6 +16934,26 @@ async function handleSlashCommand(input, ctx) {
16578
16934
  }
16579
16935
  return "handled";
16580
16936
  }
16937
+ case "compact":
16938
+ case "gc": {
16939
+ if (!ctx.hasActiveTask?.()) {
16940
+ renderWarning("No active task to compact. Context compaction requires a running task.");
16941
+ return "handled";
16942
+ }
16943
+ const validStrategies = ["default", "aggressive", "decisions", "errors", "summary"];
16944
+ const strategy = arg && validStrategies.includes(arg) ? arg : "default";
16945
+ if (arg && !validStrategies.includes(arg)) {
16946
+ renderWarning(`Unknown strategy "${arg}". Valid: ${validStrategies.join(", ")}`);
16947
+ return "handled";
16948
+ }
16949
+ const ok = ctx.requestCompaction?.(strategy) ?? false;
16950
+ if (ok) {
16951
+ renderInfo(`Compaction requested (strategy: ${c2.bold(strategy)}). Will apply at next turn boundary.`);
16952
+ } else {
16953
+ renderWarning("Could not request compaction.");
16954
+ }
16955
+ return "handled";
16956
+ }
16581
16957
  default: {
16582
16958
  const skills = discoverSkills(ctx.repoRoot);
16583
16959
  const skill = skills.find((s) => s.name === cmd || s.name === cmd.replace(/_/g, "-"));
@@ -21610,7 +21986,9 @@ async function startInteractive(config, repoPath) {
21610
21986
  "/skills",
21611
21987
  "/pause",
21612
21988
  "/stop",
21613
- "/resume"
21989
+ "/resume",
21990
+ "/compact",
21991
+ "/gc"
21614
21992
  ];
21615
21993
  const discoveredSkillNames = discoverSkills(repoRoot).map((s) => `/${s.name}`);
21616
21994
  const allCompletions = [.../* @__PURE__ */ new Set([...BUILTIN_COMMANDS, ...discoveredSkillNames])].sort();
@@ -21901,6 +22279,12 @@ async function startInteractive(config, repoPath) {
21901
22279
  statusBar.setCapabilities(caps);
21902
22280
  },
21903
22281
  hasActiveTask: () => activeTask !== null,
22282
+ requestCompaction(strategy) {
22283
+ if (!activeTask)
22284
+ return false;
22285
+ activeTask.runner.requestCompaction(strategy);
22286
+ return true;
22287
+ },
21904
22288
  abortTask() {
21905
22289
  if (!activeTask)
21906
22290
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.31.3",
3
+ "version": "0.31.4",
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",