open-agents-ai 0.11.4 → 0.11.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 +682 -34
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6831,36 +6831,112 @@ ${marker}` : marker);
6831
6831
  const middle = messages.slice(2, -keepRecent);
6832
6832
  if (middle.length === 0)
6833
6833
  return messages;
6834
- const summary = this.summarizeCompactedMessages(middle);
6834
+ let previousSummary = "";
6835
+ const nonCompactionMiddle = [];
6836
+ for (const msg of middle) {
6837
+ if (msg.role === "system" && typeof msg.content === "string" && msg.content.startsWith("[Context compacted")) {
6838
+ previousSummary = msg.content.replace(/^\[Context compacted[^\]]*\]\s*/, "").replace(/\n\n\[Continue from[^\]]*\]\s*$/, "").trim();
6839
+ } else {
6840
+ nonCompactionMiddle.push(msg);
6841
+ }
6842
+ }
6843
+ const newSummary = this.summarizeCompactedMessages(nonCompactionMiddle);
6844
+ const combinedSummary = previousSummary ? this.progressiveSummarize(previousSummary, newSummary) : newSummary;
6835
6845
  this.emit({
6836
6846
  type: "compaction",
6837
- content: `Compacted ${middle.length} messages`,
6847
+ content: `Compacted ${middle.length} messages${previousSummary ? " (progressive)" : ""}`,
6838
6848
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
6839
6849
  });
6840
6850
  const compactionMsg = {
6841
6851
  role: "system",
6842
6852
  content: `[Context compacted \u2014 summary of earlier work]
6843
6853
 
6844
- ${summary}
6854
+ ${combinedSummary}
6845
6855
 
6846
6856
  [Continue from the recent context below. Do not repeat work already completed above.]`
6847
6857
  };
6848
6858
  return [...head, compactionMsg, ...recent];
6849
6859
  }
6850
6860
  /**
6851
- * Extract a structured summary from compacted messages, preserving:
6852
- * - Files read/written/edited
6853
- * - Shell commands run and their outcomes
6854
- * - Key decisions and findings
6861
+ * Progressive summarization: merge an older compacted summary with a newer one.
6862
+ * When the combined text exceeds the budget, condense the older summary.
6863
+ */
6864
+ progressiveSummarize(olderSummary, newerSummary) {
6865
+ const MAX_SUMMARY_CHARS = 4e3;
6866
+ const combined = `${olderSummary}
6867
+
6868
+ ---
6869
+
6870
+ ${newerSummary}`;
6871
+ if (combined.length <= MAX_SUMMARY_CHARS) {
6872
+ return combined;
6873
+ }
6874
+ const condensed = this.condenseSummary(olderSummary);
6875
+ const result = `${condensed}
6876
+
6877
+ ---
6878
+
6879
+ ${newerSummary}`;
6880
+ if (result.length > MAX_SUMMARY_CHARS) {
6881
+ const budget = MAX_SUMMARY_CHARS - newerSummary.length - 60;
6882
+ return budget > 200 ? `[Earlier work, condensed]
6883
+ ${olderSummary.slice(0, budget)}...
6884
+
6885
+ ---
6886
+
6887
+ ${newerSummary}` : newerSummary;
6888
+ }
6889
+ return result;
6890
+ }
6891
+ /**
6892
+ * Condense a summary by keeping section headings and only first 2 items under each.
6893
+ */
6894
+ condenseSummary(summary) {
6895
+ const lines = summary.split("\n");
6896
+ const condensed = [];
6897
+ let itemCount = 0;
6898
+ for (const line of lines) {
6899
+ if (line.startsWith("##") || line.startsWith("---") || line.trim() === "") {
6900
+ condensed.push(line);
6901
+ itemCount = 0;
6902
+ } else if (line.startsWith("- ") || line.startsWith(" - ")) {
6903
+ if (itemCount < 2) {
6904
+ condensed.push(line);
6905
+ } else if (itemCount === 2) {
6906
+ condensed.push(" - ...(condensed)");
6907
+ }
6908
+ itemCount++;
6909
+ } else {
6910
+ condensed.push(line);
6911
+ }
6912
+ }
6913
+ return condensed.join("\n");
6914
+ }
6915
+ /**
6916
+ * Extract a rich structured summary from compacted messages, preserving:
6917
+ * - Assistant analysis/reasoning text
6918
+ * - File contents examined (key snippets, not just names)
6919
+ * - Specific code changes (old_string → new_string)
6920
+ * - Shell command results (test pass/fail, build errors)
6921
+ * - Search findings (grep/find results)
6855
6922
  * - Errors encountered
6856
6923
  */
6857
6924
  summarizeCompactedMessages(messages) {
6858
- const filesRead = /* @__PURE__ */ new Set();
6859
- const filesModified = /* @__PURE__ */ new Set();
6860
- const commandsRun = [];
6925
+ const toolCallMap = /* @__PURE__ */ new Map();
6926
+ const assistantAnalysis = [];
6927
+ const filesRead = /* @__PURE__ */ new Map();
6928
+ const filesModified = /* @__PURE__ */ new Map();
6929
+ const commandResults = [];
6930
+ const searchFindings = [];
6861
6931
  const errors = [];
6862
6932
  let toolCallCount = 0;
6863
6933
  for (const msg of messages) {
6934
+ if (msg.role === "assistant" && typeof msg.content === "string" && msg.content.trim()) {
6935
+ const text = msg.content.trim();
6936
+ if (text.length > 20) {
6937
+ assistantAnalysis.push(text.length > 400 ? text.slice(0, 400) + "..." : text);
6938
+ }
6939
+ }
6864
6940
  if (msg.tool_calls) {
6865
6941
  for (const tc of msg.tool_calls) {
6866
6942
  toolCallCount++;
@@ -6871,37 +6947,134 @@ ${summary}
6871
6947
  return {};
6872
6948
  }
6873
6949
  })();
6950
+ toolCallMap.set(tc.id, { name: tc.function.name, args });
6874
6951
  const name = tc.function.name;
6875
- if (name === "file_read") {
6876
- filesRead.add(String(args.path || ""));
6877
- } else if (name === "file_write" || name === "file_edit" || name === "batch_edit") {
6878
- filesModified.add(String(args.path || ""));
6879
- } else if (name === "shell" || name === "background_run") {
6880
- const cmd = String(args.command || "").slice(0, 80);
6881
- if (cmd)
6882
- commandsRun.push(cmd);
6952
+ if (name === "file_edit") {
6953
+ const path = String(args.path || "");
6954
+ const oldStr = String(args.old_string || "").slice(0, 100);
6955
+ const newStr = String(args.new_string || "").slice(0, 100);
6956
+ if (path) {
6957
+ const changes = filesModified.get(path) || [];
6958
+ changes.push(`"${oldStr}" \u2192 "${newStr}"`);
6959
+ filesModified.set(path, changes);
6960
+ }
6961
+ } else if (name === "file_write") {
6962
+ const path = String(args.path || "");
6963
+ if (path) {
6964
+ if (!filesModified.has(path))
6965
+ filesModified.set(path, []);
6966
+ filesModified.get(path).push("(full file write)");
6967
+ }
6968
+ } else if (name === "batch_edit") {
6969
+ const path = String(args.path || "");
6970
+ if (path) {
6971
+ if (!filesModified.has(path))
6972
+ filesModified.set(path, []);
6973
+ filesModified.get(path).push("(batch edit)");
6974
+ }
6883
6975
  }
6884
6976
  }
6885
6977
  }
6886
- if (msg.role === "tool" && typeof msg.content === "string") {
6887
- if (msg.content.startsWith("Error:")) {
6888
- errors.push(msg.content.slice(0, 150));
6978
+ if (msg.role === "tool" && typeof msg.content === "string" && msg.tool_call_id) {
6979
+ const tc = toolCallMap.get(msg.tool_call_id);
6980
+ const content = msg.content;
6981
+ if (!tc) {
6982
+ if (content.startsWith("Error:"))
6983
+ errors.push(content.slice(0, 200));
6984
+ continue;
6985
+ }
6986
+ switch (tc.name) {
6987
+ case "file_read": {
6988
+ const path = String(tc.args.path || "");
6989
+ const lines = content.split("\n");
6990
+ const summary = lines.length > 5 ? `${lines.length} lines \u2014 ${lines.slice(0, 2).join("; ").slice(0, 120)}...` : content.slice(0, 150);
6991
+ if (path)
6992
+ filesRead.set(path, summary);
6993
+ break;
6994
+ }
6995
+ case "shell":
6996
+ case "background_run": {
6997
+ const cmd = String(tc.args.command || "").slice(0, 100);
6998
+ const hasError = content.startsWith("Error:") || /FAIL|ERR!/i.test(content);
6999
+ const hasPass = /PASS|passed|✓|success/i.test(content);
7000
+ let outcome;
7001
+ if (hasError) {
7002
+ const errorLines = content.split("\n").filter((l) => /error|FAIL|✗|×|ERR!/i.test(l)).slice(0, 3);
7003
+ outcome = errorLines.length > 0 ? errorLines.join("; ").slice(0, 200) : content.slice(0, 200);
7004
+ errors.push(`\`${cmd}\`: ${outcome.slice(0, 150)}`);
7005
+ } else if (hasPass) {
7006
+ outcome = "passed";
7007
+ } else {
7008
+ outcome = content.slice(0, 150);
7009
+ }
7010
+ commandResults.push({ cmd, outcome });
7011
+ break;
7012
+ }
7013
+ case "grep_search": {
7014
+ const pattern = String(tc.args.pattern || "");
7015
+ const matchCount = (content.match(/\n/g) || []).length;
7016
+ searchFindings.push(`grep "${pattern}": ${matchCount} matches \u2014 ${content.slice(0, 150)}`);
7017
+ break;
7018
+ }
7019
+ case "find_files": {
7020
+ const pattern = String(tc.args.pattern || "");
7021
+ const files = content.split("\n").filter(Boolean);
7022
+ searchFindings.push(`find "${pattern}": ${files.length} files \u2014 ${files.slice(0, 5).join(", ")}`);
7023
+ break;
7024
+ }
7025
+ default: {
7026
+ if (content.startsWith("Error:"))
7027
+ errors.push(`${tc.name}: ${content.slice(0, 200)}`);
7028
+ }
6889
7029
  }
6890
7030
  }
6891
7031
  }
6892
7032
  const parts = [];
6893
- parts.push(`${toolCallCount} tool calls were made in the compacted section.`);
7033
+ parts.push(`## Compacted Work Summary (${toolCallCount} tool calls)
7034
+ `);
7035
+ if (assistantAnalysis.length > 0) {
7036
+ parts.push("### Agent Analysis");
7037
+ for (const analysis of assistantAnalysis.slice(-5)) {
7038
+ parts.push(`- ${analysis}`);
7039
+ }
7040
+ parts.push("");
7041
+ }
6894
7042
  if (filesRead.size > 0) {
6895
- parts.push(`Files read: ${Array.from(filesRead).slice(0, 15).join(", ")}`);
7043
+ parts.push("### Files Examined");
7044
+ for (const [path, summary] of Array.from(filesRead).slice(0, 15)) {
7045
+ parts.push(`- \`${path}\`: ${summary}`);
7046
+ }
7047
+ parts.push("");
6896
7048
  }
6897
7049
  if (filesModified.size > 0) {
6898
- parts.push(`Files modified: ${Array.from(filesModified).slice(0, 15).join(", ")}`);
7050
+ parts.push("### Code Changes Made");
7051
+ for (const [path, changes] of Array.from(filesModified).slice(0, 10)) {
7052
+ parts.push(`- **${path}**:`);
7053
+ for (const change of changes.slice(0, 3)) {
7054
+ parts.push(` - ${change}`);
7055
+ }
7056
+ }
7057
+ parts.push("");
6899
7058
  }
6900
- if (commandsRun.length > 0) {
6901
- parts.push(`Commands run: ${commandsRun.slice(0, 8).join("; ")}`);
7059
+ if (commandResults.length > 0) {
7060
+ parts.push("### Commands Executed");
7061
+ for (const { cmd, outcome } of commandResults.slice(0, 8)) {
7062
+ parts.push(`- \`${cmd}\` \u2192 ${outcome}`);
7063
+ }
7064
+ parts.push("");
7065
+ }
7066
+ if (searchFindings.length > 0) {
7067
+ parts.push("### Search Findings");
7068
+ for (const finding of searchFindings.slice(0, 5)) {
7069
+ parts.push(`- ${finding}`);
7070
+ }
7071
+ parts.push("");
6902
7072
  }
6903
7073
  if (errors.length > 0) {
6904
- parts.push(`Errors encountered: ${errors.slice(0, 3).join("; ")}`);
7074
+ parts.push("### Errors Encountered");
7075
+ for (const error of errors.slice(0, 5)) {
7076
+ parts.push(`- ${error}`);
7077
+ }
6905
7078
  }
6906
7079
  return parts.join("\n");
6907
7080
  }
@@ -8815,14 +8988,62 @@ function getEnvironment(repoRoot) {
8815
8988
  ];
8816
8989
  return lines.join("\n");
8817
8990
  }
8818
- function buildProjectContext(repoRoot) {
8991
+ function loadTaskMemories(repoRoot, store) {
8992
+ try {
8993
+ let tasks = store.listByRepo(repoRoot);
8994
+ if (tasks.length === 0) {
8995
+ tasks = store.recent(10);
8996
+ }
8997
+ if (tasks.length === 0)
8998
+ return "";
8999
+ const lines = ["Recent agent tasks (cross-session memory):"];
9000
+ for (const t of tasks.slice(0, 10)) {
9001
+ const date = t.createdAt.split("T")[0];
9002
+ const files = t.filesTouched.slice(0, 5).join(", ");
9003
+ lines.push(`- [${date}] ${t.goal.slice(0, 100)} \u2192 ${t.outcome}${files ? ` (files: ${files})` : ""}`);
9004
+ if (t.notes) {
9005
+ lines.push(` Notes: ${t.notes.slice(0, 150)}`);
9006
+ }
9007
+ }
9008
+ return lines.join("\n");
9009
+ } catch {
9010
+ return "";
9011
+ }
9012
+ }
9013
+ function loadFailurePatterns(store) {
9014
+ try {
9015
+ const unresolved = store.listUnresolved();
9016
+ if (unresolved.length === 0)
9017
+ return "";
9018
+ const seen = /* @__PURE__ */ new Set();
9019
+ const unique = unresolved.filter((f) => {
9020
+ if (seen.has(f.fingerprint))
9021
+ return false;
9022
+ seen.add(f.fingerprint);
9023
+ return true;
9024
+ });
9025
+ if (unique.length === 0)
9026
+ return "";
9027
+ const lines = ["Known failure patterns (avoid repeating these):"];
9028
+ for (const f of unique.slice(0, 8)) {
9029
+ const file = f.filePath ? ` in ${f.filePath}` : "";
9030
+ lines.push(`- [${f.failureType}]${file}: ${f.errorMessage.slice(0, 150)}`);
9031
+ }
9032
+ return lines.join("\n");
9033
+ } catch {
9034
+ return "";
9035
+ }
9036
+ }
9037
+ function buildProjectContext(repoRoot, stores) {
8819
9038
  return {
8820
9039
  projectInstructions: loadProjectFiles(repoRoot),
8821
9040
  projectMap: loadProjectMap(repoRoot),
8822
9041
  gitInfo: getGitInfo(repoRoot),
8823
9042
  memoryContext: loadMemoryContext(repoRoot),
8824
9043
  sessionHistory: loadSessionHistory(repoRoot),
8825
- environment: getEnvironment(repoRoot)
9044
+ environment: getEnvironment(repoRoot),
9045
+ taskMemories: stores?.taskMemoryStore ? loadTaskMemories(repoRoot, stores.taskMemoryStore) : "",
9046
+ failurePatterns: stores?.failureStore ? loadFailurePatterns(stores.failureStore) : ""
8826
9047
  };
8827
9048
  }
8828
9049
  function formatContextForPrompt(ctx) {
@@ -8856,6 +9077,20 @@ Use this context to avoid re-learning known patterns. Update with memory_write i
8856
9077
  sections.push(`## Session History
8857
9078
 
8858
9079
  ${ctx.sessionHistory}`);
9080
+ }
9081
+ if (ctx.taskMemories) {
9082
+ sections.push(`## Cross-Session Task Memory
9083
+
9084
+ ${ctx.taskMemories}
9085
+
9086
+ Use this history to avoid re-doing completed work and to learn from past approaches.`);
9087
+ }
9088
+ if (ctx.failurePatterns) {
9089
+ sections.push(`## Known Failure Patterns
9090
+
9091
+ ${ctx.failurePatterns}
9092
+
9093
+ Avoid approaches that led to these failures. If you encounter these errors, try a different strategy.`);
8859
9094
  }
8860
9095
  return sections.join("\n\n");
8861
9096
  }
@@ -8866,6 +9101,354 @@ var init_project_context = __esm({
8866
9101
  }
8867
9102
  });
8868
9103
 
9104
+ // packages/memory/dist/db.js
9105
+ import Database from "better-sqlite3";
9106
+ function initDb(dbPath) {
9107
+ const db = new Database(dbPath);
9108
+ db.pragma("journal_mode = WAL");
9109
+ db.pragma("foreign_keys = ON");
9110
+ runMigrations(db);
9111
+ return db;
9112
+ }
9113
+ function closeDb(db) {
9114
+ db.close();
9115
+ }
9116
+ function runMigrations(db) {
9117
+ db.exec(`
9118
+ -- repo_profiles: one row per repository root.
9119
+ CREATE TABLE IF NOT EXISTS repo_profiles (
9120
+ repo_root TEXT PRIMARY KEY,
9121
+ languages TEXT NOT NULL DEFAULT '[]', -- JSON array
9122
+ frameworks TEXT NOT NULL DEFAULT '[]', -- JSON array
9123
+ build_system TEXT NOT NULL DEFAULT '',
9124
+ test_commands TEXT NOT NULL DEFAULT '[]', -- JSON array
9125
+ lint_commands TEXT NOT NULL DEFAULT '[]', -- JSON array
9126
+ package_manager TEXT NOT NULL DEFAULT '',
9127
+ notes TEXT,
9128
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
9129
+ updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
9130
+ );
9131
+
9132
+ -- file_summaries: one row per (repo_root, file_path) pair.
9133
+ CREATE TABLE IF NOT EXISTS file_summaries (
9134
+ file_path TEXT NOT NULL,
9135
+ repo_root TEXT NOT NULL,
9136
+ purpose TEXT NOT NULL DEFAULT '',
9137
+ exports TEXT NOT NULL DEFAULT '[]', -- JSON array
9138
+ imports TEXT NOT NULL DEFAULT '[]', -- JSON array
9139
+ domain TEXT NOT NULL DEFAULT '',
9140
+ risk_level TEXT NOT NULL DEFAULT 'low', -- low | medium | high
9141
+ related_tests TEXT NOT NULL DEFAULT '[]', -- JSON array
9142
+ notes TEXT,
9143
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
9144
+ updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
9145
+ PRIMARY KEY (file_path)
9146
+ );
9147
+
9148
+ CREATE INDEX IF NOT EXISTS idx_file_summaries_repo
9149
+ ON file_summaries (repo_root);
9150
+
9151
+ CREATE INDEX IF NOT EXISTS idx_file_summaries_domain
9152
+ ON file_summaries (domain);
9153
+
9154
+ CREATE INDEX IF NOT EXISTS idx_file_summaries_risk
9155
+ ON file_summaries (risk_level);
9156
+
9157
+ -- task_memory: historical record of agent tasks.
9158
+ CREATE TABLE IF NOT EXISTS task_memory (
9159
+ id TEXT PRIMARY KEY,
9160
+ session_id TEXT NOT NULL,
9161
+ repo_root TEXT NOT NULL,
9162
+ goal TEXT NOT NULL DEFAULT '',
9163
+ constraints TEXT NOT NULL DEFAULT '[]', -- JSON array
9164
+ files_touched TEXT NOT NULL DEFAULT '[]', -- JSON array
9165
+ patches TEXT NOT NULL DEFAULT '[]', -- JSON array
9166
+ outcome TEXT NOT NULL DEFAULT 'unknown', -- success | failure | partial | unknown
9167
+ quality_score REAL,
9168
+ notes TEXT,
9169
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
9170
+ );
9171
+
9172
+ CREATE INDEX IF NOT EXISTS idx_task_memory_session
9173
+ ON task_memory (session_id);
9174
+
9175
+ CREATE INDEX IF NOT EXISTS idx_task_memory_repo
9176
+ ON task_memory (repo_root);
9177
+
9178
+ CREATE INDEX IF NOT EXISTS idx_task_memory_outcome
9179
+ ON task_memory (outcome);
9180
+
9181
+ -- patch_history: individual file diffs applied by the agent.
9182
+ CREATE TABLE IF NOT EXISTS patch_history (
9183
+ id TEXT PRIMARY KEY,
9184
+ task_id TEXT NOT NULL,
9185
+ session_id TEXT NOT NULL,
9186
+ repo_root TEXT NOT NULL,
9187
+ file_path TEXT NOT NULL,
9188
+ diff TEXT NOT NULL DEFAULT '',
9189
+ status TEXT NOT NULL DEFAULT 'applied', -- applied | reverted | pending | failed
9190
+ applied_at TEXT,
9191
+ reverted_at TEXT,
9192
+ notes TEXT,
9193
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
9194
+ );
9195
+
9196
+ CREATE INDEX IF NOT EXISTS idx_patch_history_task
9197
+ ON patch_history (task_id);
9198
+
9199
+ CREATE INDEX IF NOT EXISTS idx_patch_history_file
9200
+ ON patch_history (file_path);
9201
+
9202
+ CREATE INDEX IF NOT EXISTS idx_patch_history_status
9203
+ ON patch_history (status);
9204
+
9205
+ -- failures: fingerprinted failure events for pattern detection.
9206
+ CREATE TABLE IF NOT EXISTS failures (
9207
+ id TEXT PRIMARY KEY,
9208
+ task_id TEXT NOT NULL,
9209
+ session_id TEXT NOT NULL,
9210
+ repo_root TEXT NOT NULL,
9211
+ failure_type TEXT NOT NULL DEFAULT '',
9212
+ fingerprint TEXT NOT NULL DEFAULT '',
9213
+ file_path TEXT,
9214
+ error_message TEXT NOT NULL DEFAULT '',
9215
+ context TEXT, -- JSON object or null
9216
+ resolved INTEGER NOT NULL DEFAULT 0, -- 0 = false, 1 = true
9217
+ resolved_at TEXT,
9218
+ notes TEXT,
9219
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
9220
+ );
9221
+
9222
+ CREATE INDEX IF NOT EXISTS idx_failures_type
9223
+ ON failures (failure_type);
9224
+
9225
+ CREATE INDEX IF NOT EXISTS idx_failures_fingerprint
9226
+ ON failures (fingerprint);
9227
+
9228
+ CREATE INDEX IF NOT EXISTS idx_failures_resolved
9229
+ ON failures (resolved);
9230
+
9231
+ -- validation_runs: results of lint / test / build / typecheck runs.
9232
+ CREATE TABLE IF NOT EXISTS validation_runs (
9233
+ id TEXT PRIMARY KEY,
9234
+ task_id TEXT NOT NULL,
9235
+ session_id TEXT NOT NULL,
9236
+ repo_root TEXT NOT NULL,
9237
+ run_type TEXT NOT NULL DEFAULT '', -- test | lint | build | typecheck
9238
+ command TEXT NOT NULL DEFAULT '',
9239
+ passed INTEGER NOT NULL DEFAULT 0, -- 0 = false, 1 = true
9240
+ duration_ms INTEGER,
9241
+ output TEXT,
9242
+ error_output TEXT,
9243
+ coverage_percent REAL,
9244
+ notes TEXT,
9245
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
9246
+ );
9247
+
9248
+ CREATE INDEX IF NOT EXISTS idx_validation_runs_task
9249
+ ON validation_runs (task_id);
9250
+
9251
+ CREATE INDEX IF NOT EXISTS idx_validation_runs_run_type
9252
+ ON validation_runs (run_type);
9253
+
9254
+ CREATE INDEX IF NOT EXISTS idx_validation_runs_passed
9255
+ ON validation_runs (passed);
9256
+ `);
9257
+ }
9258
+ var init_db = __esm({
9259
+ "packages/memory/dist/db.js"() {
9260
+ "use strict";
9261
+ }
9262
+ });
9263
+
9264
+ // packages/memory/dist/repoProfileStore.js
9265
+ var init_repoProfileStore = __esm({
9266
+ "packages/memory/dist/repoProfileStore.js"() {
9267
+ "use strict";
9268
+ }
9269
+ });
9270
+
9271
+ // packages/memory/dist/fileSummaryStore.js
9272
+ var init_fileSummaryStore = __esm({
9273
+ "packages/memory/dist/fileSummaryStore.js"() {
9274
+ "use strict";
9275
+ }
9276
+ });
9277
+
9278
+ // packages/memory/dist/taskMemoryStore.js
9279
+ import { randomUUID } from "node:crypto";
9280
+ function rowToTask(row) {
9281
+ return {
9282
+ id: row.id,
9283
+ sessionId: row.session_id,
9284
+ repoRoot: row.repo_root,
9285
+ goal: row.goal,
9286
+ constraints: JSON.parse(row.constraints),
9287
+ filesTouched: JSON.parse(row.files_touched),
9288
+ patches: JSON.parse(row.patches),
9289
+ outcome: row.outcome,
9290
+ qualityScore: row.quality_score ?? null,
9291
+ notes: row.notes ?? null,
9292
+ createdAt: row.created_at
9293
+ };
9294
+ }
9295
+ var TaskMemoryStore;
9296
+ var init_taskMemoryStore = __esm({
9297
+ "packages/memory/dist/taskMemoryStore.js"() {
9298
+ "use strict";
9299
+ TaskMemoryStore = class {
9300
+ db;
9301
+ constructor(db) {
9302
+ this.db = db;
9303
+ }
9304
+ /**
9305
+ * Insert a new task record and return its auto-generated id.
9306
+ */
9307
+ insert(input) {
9308
+ const id = randomUUID();
9309
+ const now = (/* @__PURE__ */ new Date()).toISOString();
9310
+ this.db.prepare(`INSERT INTO task_memory
9311
+ (id, session_id, repo_root, goal, constraints,
9312
+ files_touched, patches, outcome, quality_score, notes, created_at)
9313
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, input.sessionId, input.repoRoot, input.goal, JSON.stringify(input.constraints), JSON.stringify(input.filesTouched), JSON.stringify(input.patches), input.outcome, input.qualityScore ?? null, input.notes ?? null, now);
9314
+ return id;
9315
+ }
9316
+ /** Retrieve a task record by id, or `null` if not found. */
9317
+ getById(id) {
9318
+ const row = this.db.prepare("SELECT * FROM task_memory WHERE id = ?").get(id);
9319
+ return row ? rowToTask(row) : null;
9320
+ }
9321
+ /** List all tasks associated with a session. */
9322
+ listBySession(sessionId) {
9323
+ const rows = this.db.prepare("SELECT * FROM task_memory WHERE session_id = ? ORDER BY created_at DESC").all(sessionId);
9324
+ return rows.map(rowToTask);
9325
+ }
9326
+ /** List all tasks for a repository root. */
9327
+ listByRepo(repoRoot) {
9328
+ const rows = this.db.prepare("SELECT * FROM task_memory WHERE repo_root = ? ORDER BY created_at DESC").all(repoRoot);
9329
+ return rows.map(rowToTask);
9330
+ }
9331
+ /** List tasks filtered by outcome. */
9332
+ listByOutcome(outcome) {
9333
+ const rows = this.db.prepare("SELECT * FROM task_memory WHERE outcome = ? ORDER BY created_at DESC").all(outcome);
9334
+ return rows.map(rowToTask);
9335
+ }
9336
+ /**
9337
+ * Return the N most recently created tasks across all sessions and repos.
9338
+ * Useful for building a rolling context window.
9339
+ */
9340
+ recent(limit) {
9341
+ const rows = this.db.prepare("SELECT * FROM task_memory ORDER BY created_at DESC LIMIT ?").all(limit);
9342
+ return rows.map(rowToTask);
9343
+ }
9344
+ };
9345
+ }
9346
+ });
9347
+
9348
+ // packages/memory/dist/patchHistoryStore.js
9349
+ var init_patchHistoryStore = __esm({
9350
+ "packages/memory/dist/patchHistoryStore.js"() {
9351
+ "use strict";
9352
+ }
9353
+ });
9354
+
9355
+ // packages/memory/dist/failureStore.js
9356
+ import { randomUUID as randomUUID2 } from "node:crypto";
9357
+ function rowToFailure(row) {
9358
+ return {
9359
+ id: row.id,
9360
+ taskId: row.task_id,
9361
+ sessionId: row.session_id,
9362
+ repoRoot: row.repo_root,
9363
+ failureType: row.failure_type,
9364
+ fingerprint: row.fingerprint,
9365
+ filePath: row.file_path ?? null,
9366
+ errorMessage: row.error_message,
9367
+ context: row.context !== null ? JSON.parse(row.context) : null,
9368
+ resolved: row.resolved === 1,
9369
+ resolvedAt: row.resolved_at ?? null,
9370
+ notes: row.notes ?? null,
9371
+ createdAt: row.created_at
9372
+ };
9373
+ }
9374
+ var FailureStore;
9375
+ var init_failureStore = __esm({
9376
+ "packages/memory/dist/failureStore.js"() {
9377
+ "use strict";
9378
+ FailureStore = class {
9379
+ db;
9380
+ constructor(db) {
9381
+ this.db = db;
9382
+ }
9383
+ /**
9384
+ * Record a new failure event and return its auto-generated id.
9385
+ */
9386
+ insert(input) {
9387
+ const id = randomUUID2();
9388
+ const now = (/* @__PURE__ */ new Date()).toISOString();
9389
+ this.db.prepare(`INSERT INTO failures
9390
+ (id, task_id, session_id, repo_root, failure_type,
9391
+ fingerprint, file_path, error_message, context,
9392
+ resolved, resolved_at, notes, created_at)
9393
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, input.taskId, input.sessionId, input.repoRoot, input.failureType, input.fingerprint, input.filePath ?? null, input.errorMessage, input.context !== null ? JSON.stringify(input.context) : null, input.resolved ? 1 : 0, input.resolvedAt ?? null, input.notes ?? null, now);
9394
+ return id;
9395
+ }
9396
+ /** Retrieve a failure by id, or `null` if not found. */
9397
+ getById(id) {
9398
+ const row = this.db.prepare("SELECT * FROM failures WHERE id = ?").get(id);
9399
+ return row ? rowToFailure(row) : null;
9400
+ }
9401
+ /** List failures filtered by type. */
9402
+ listByType(failureType) {
9403
+ const rows = this.db.prepare("SELECT * FROM failures WHERE failure_type = ? ORDER BY created_at DESC").all(failureType);
9404
+ return rows.map(rowToFailure);
9405
+ }
9406
+ /** List all failures sharing the same content fingerprint. */
9407
+ listByFingerprint(fingerprint) {
9408
+ const rows = this.db.prepare("SELECT * FROM failures WHERE fingerprint = ? ORDER BY created_at DESC").all(fingerprint);
9409
+ return rows.map(rowToFailure);
9410
+ }
9411
+ /** List all failures that have not yet been marked as resolved. */
9412
+ listUnresolved() {
9413
+ const rows = this.db.prepare("SELECT * FROM failures WHERE resolved = 0 ORDER BY created_at DESC").all();
9414
+ return rows.map(rowToFailure);
9415
+ }
9416
+ /**
9417
+ * Mark a failure as resolved.
9418
+ *
9419
+ * @param id - The failure record id.
9420
+ * @param resolvedAt - ISO timestamp string of resolution time.
9421
+ */
9422
+ markResolved(id, resolvedAt) {
9423
+ this.db.prepare(`UPDATE failures
9424
+ SET resolved = 1, resolved_at = ?
9425
+ WHERE id = ?`).run(resolvedAt, id);
9426
+ }
9427
+ };
9428
+ }
9429
+ });
9430
+
9431
+ // packages/memory/dist/validationStore.js
9432
+ var init_validationStore = __esm({
9433
+ "packages/memory/dist/validationStore.js"() {
9434
+ "use strict";
9435
+ }
9436
+ });
9437
+
9438
+ // packages/memory/dist/index.js
9439
+ var init_dist6 = __esm({
9440
+ "packages/memory/dist/index.js"() {
9441
+ "use strict";
9442
+ init_db();
9443
+ init_repoProfileStore();
9444
+ init_fileSummaryStore();
9445
+ init_taskMemoryStore();
9446
+ init_patchHistoryStore();
9447
+ init_failureStore();
9448
+ init_validationStore();
9449
+ }
9450
+ });
9451
+
8869
9452
  // packages/cli/dist/tui/carousel.js
8870
9453
  function fg(code, text) {
8871
9454
  return isTTY3 ? `\x1B[38;5;${code}m${text}\x1B[0m` : text;
@@ -10097,8 +10680,8 @@ Use task_status("${taskId}") or task_output("${taskId}") to check progress.`
10097
10680
  }
10098
10681
  };
10099
10682
  }
10100
- function startTask(task, config, repoRoot, voice, stream) {
10101
- const projectCtx = buildProjectContext(repoRoot);
10683
+ function startTask(task, config, repoRoot, voice, stream, taskStores) {
10684
+ const projectCtx = buildProjectContext(repoRoot, taskStores?.contextStores);
10102
10685
  const dynamicContext = formatContextForPrompt(projectCtx);
10103
10686
  const backend = new OllamaAgenticBackend(config.backendUrl.replace(/\/$/, ""), config.model);
10104
10687
  const runner = new AgenticRunner(backend, {
@@ -10112,9 +10695,16 @@ function startTask(task, config, repoRoot, voice, stream) {
10112
10695
  streamEnabled: stream?.enabled ?? false
10113
10696
  });
10114
10697
  runner.registerTools(buildTools(repoRoot, config));
10698
+ const filesTouched = /* @__PURE__ */ new Set();
10115
10699
  runner.onEvent((event) => {
10116
10700
  switch (event.type) {
10117
10701
  case "tool_call":
10702
+ if (event.toolArgs?.path && typeof event.toolArgs.path === "string") {
10703
+ const name = event.toolName ?? "";
10704
+ if (name === "file_write" || name === "file_edit" || name === "batch_edit") {
10705
+ filesTouched.add(event.toolArgs.path);
10706
+ }
10707
+ }
10118
10708
  if (voice?.enabled) {
10119
10709
  const desc = describeToolCall(event.toolName ?? "unknown", event.toolArgs ?? {});
10120
10710
  renderVoiceText(desc);
@@ -10189,6 +10779,22 @@ function startTask(task, config, repoRoot, voice, stream) {
10189
10779
  });
10190
10780
  } catch {
10191
10781
  }
10782
+ if (taskStores?.taskMemoryStore) {
10783
+ try {
10784
+ taskStores.taskMemoryStore.insert({
10785
+ sessionId,
10786
+ repoRoot,
10787
+ goal: task.slice(0, 500),
10788
+ constraints: [],
10789
+ filesTouched: Array.from(filesTouched).slice(0, 50),
10790
+ patches: [],
10791
+ outcome: result.completed ? "success" : "partial",
10792
+ qualityScore: null,
10793
+ notes: result.summary.slice(0, 500)
10794
+ });
10795
+ } catch {
10796
+ }
10797
+ }
10192
10798
  });
10193
10799
  return { runner, promise };
10194
10800
  }
@@ -10200,6 +10806,17 @@ async function startInteractive(config, repoPath) {
10200
10806
  }
10201
10807
  initOaDirectory(repoRoot);
10202
10808
  const savedSettings = resolveSettings(repoRoot);
10809
+ let memoryDb = null;
10810
+ let taskMemoryStore = null;
10811
+ let failureStore = null;
10812
+ let contextStores;
10813
+ try {
10814
+ memoryDb = initDb(config.dbPath);
10815
+ taskMemoryStore = new TaskMemoryStore(memoryDb);
10816
+ failureStore = new FailureStore(memoryDb);
10817
+ contextStores = { taskMemoryStore, failureStore };
10818
+ } catch {
10819
+ }
10203
10820
  if (savedSettings.model)
10204
10821
  config = { ...config, model: savedSettings.model };
10205
10822
  if (savedSettings.backendUrl)
@@ -10330,6 +10947,12 @@ async function startInteractive(config, repoPath) {
10330
10947
  if (carousel.isRunning)
10331
10948
  carousel.stop();
10332
10949
  voiceEngine.dispose();
10950
+ if (memoryDb) {
10951
+ try {
10952
+ closeDb(memoryDb);
10953
+ } catch {
10954
+ }
10955
+ }
10333
10956
  rl.close();
10334
10957
  },
10335
10958
  async voiceToggle() {
@@ -10421,12 +11044,36 @@ ${c2.dim("Goodbye!")}
10421
11044
  const task = startTask(fullInput, currentConfig, repoRoot, voiceEngine, {
10422
11045
  enabled: streamEnabled,
10423
11046
  renderer: streamRenderer
11047
+ }, {
11048
+ contextStores,
11049
+ taskMemoryStore: taskMemoryStore ?? void 0,
11050
+ failureStore: failureStore ?? void 0
10424
11051
  });
10425
11052
  activeTask = task;
10426
11053
  showPrompt();
10427
11054
  await task.promise;
10428
11055
  } catch (err) {
10429
- renderError(err instanceof Error ? err.message : String(err));
11056
+ const errMsg = err instanceof Error ? err.message : String(err);
11057
+ renderError(errMsg);
11058
+ if (failureStore) {
11059
+ try {
11060
+ const { createHash: createHash2 } = await import("node:crypto");
11061
+ failureStore.insert({
11062
+ taskId: "",
11063
+ sessionId: `${Date.now()}`,
11064
+ repoRoot,
11065
+ failureType: "runtime-error",
11066
+ fingerprint: createHash2("sha256").update(errMsg.slice(0, 200)).digest("hex").slice(0, 16),
11067
+ filePath: null,
11068
+ errorMessage: errMsg.slice(0, 500),
11069
+ context: null,
11070
+ resolved: false,
11071
+ resolvedAt: null,
11072
+ notes: `Task: ${fullInput.slice(0, 200)}`
11073
+ });
11074
+ } catch {
11075
+ }
11076
+ }
10430
11077
  } finally {
10431
11078
  activeTask = null;
10432
11079
  }
@@ -10513,6 +11160,7 @@ var init_interactive = __esm({
10513
11160
  init_commands();
10514
11161
  init_setup();
10515
11162
  init_project_context();
11163
+ init_dist6();
10516
11164
  init_oa_directory();
10517
11165
  init_render();
10518
11166
  init_carousel();
@@ -10715,7 +11363,7 @@ var init_embeddings = __esm({
10715
11363
  });
10716
11364
 
10717
11365
  // packages/indexer/dist/index.js
10718
- var init_dist6 = __esm({
11366
+ var init_dist7 = __esm({
10719
11367
  "packages/indexer/dist/index.js"() {
10720
11368
  "use strict";
10721
11369
  init_codebase_indexer();
@@ -10822,7 +11470,7 @@ async function indexRepoCommand(opts, _config) {
10822
11470
  var init_index_repo = __esm({
10823
11471
  "packages/cli/dist/commands/index-repo.js"() {
10824
11472
  "use strict";
10825
- init_dist6();
11473
+ init_dist7();
10826
11474
  init_spinner();
10827
11475
  init_output();
10828
11476
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.11.4",
3
+ "version": "0.11.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",