open-agents-ai 0.11.4 → 0.11.6

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 +730 -41
  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("");
7058
+ }
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("");
6899
7065
  }
6900
- if (commandsRun.length > 0) {
6901
- parts.push(`Commands run: ${commandsRun.slice(0, 8).join("; ")}`);
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;
@@ -9330,15 +9913,17 @@ var init_voice = __esm({
9330
9913
  }
9331
9914
  /**
9332
9915
  * Speak text asynchronously (non-blocking).
9333
- * Queues if already speaking; drops if queue gets too deep.
9916
+ * Long text is chunked on sentence/line boundaries for reliable TTS.
9917
+ * Chunks are queued FIFO and played back-to-back without cutoff.
9334
9918
  */
9335
9919
  speak(text) {
9336
9920
  if (!this.enabled || !this.ready)
9337
9921
  return;
9338
- if (this.speakQueue.length >= 3) {
9922
+ const chunks = this.chunkText(text);
9923
+ if (this.speakQueue.length >= 30) {
9339
9924
  this.speakQueue.length = 0;
9340
9925
  }
9341
- this.speakQueue.push(text);
9926
+ this.speakQueue.push(...chunks);
9342
9927
  if (!this.speaking) {
9343
9928
  this.drainQueue().catch(() => {
9344
9929
  });
@@ -9352,13 +9937,47 @@ var init_voice = __esm({
9352
9937
  this.config = null;
9353
9938
  }
9354
9939
  // -------------------------------------------------------------------------
9940
+ // Text chunking for long TTS input
9941
+ // -------------------------------------------------------------------------
9942
+ /**
9943
+ * Split long text into sentence-sized chunks suitable for ONNX TTS.
9944
+ * Splits on: newlines (list items, paragraphs), then sentence-ending
9945
+ * punctuation (.!?). Short text (<= 200 chars) passes through unchanged.
9946
+ */
9947
+ chunkText(text) {
9948
+ if (text.length <= 200)
9949
+ return [text];
9950
+ const chunks = [];
9951
+ const lines = text.split(/\n+/);
9952
+ for (const line of lines) {
9953
+ const trimmed = line.replace(/^[\s\-*•]+/, "").trim();
9954
+ if (!trimmed)
9955
+ continue;
9956
+ if (trimmed.length <= 200) {
9957
+ chunks.push(trimmed);
9958
+ } else {
9959
+ const sentences = trimmed.split(/(?<=[.!?])\s+/);
9960
+ for (const sentence of sentences) {
9961
+ const s = sentence.trim();
9962
+ if (s)
9963
+ chunks.push(s);
9964
+ }
9965
+ }
9966
+ }
9967
+ return chunks.length > 0 ? chunks : [text.slice(0, 200)];
9968
+ }
9969
+ // -------------------------------------------------------------------------
9355
9970
  // Queue drain
9356
9971
  // -------------------------------------------------------------------------
9972
+ /**
9973
+ * Drain the speak queue FIFO — each item is synthesized and played to
9974
+ * completion before the next starts. Items play back-to-back without
9975
+ * cutting off previous audio.
9976
+ */
9357
9977
  async drainQueue() {
9358
9978
  this.speaking = true;
9359
9979
  while (this.speakQueue.length > 0) {
9360
- const text = this.speakQueue.pop();
9361
- this.speakQueue.length = 0;
9980
+ const text = this.speakQueue.shift();
9362
9981
  try {
9363
9982
  await this.synthesizeAndPlay(text);
9364
9983
  } catch {
@@ -9478,7 +10097,6 @@ var init_voice = __esm({
9478
10097
  // Audio playback (system default speakers)
9479
10098
  // -------------------------------------------------------------------------
9480
10099
  async playWav(path) {
9481
- this.killPlayback();
9482
10100
  const cmd = this.getPlayCommand(path);
9483
10101
  if (!cmd)
9484
10102
  return;
@@ -9499,7 +10117,13 @@ var init_voice = __esm({
9499
10117
  resolve14();
9500
10118
  });
9501
10119
  setTimeout(() => {
9502
- this.killPlayback();
10120
+ if (this.currentPlayback === child) {
10121
+ try {
10122
+ child.kill("SIGTERM");
10123
+ } catch {
10124
+ }
10125
+ this.currentPlayback = null;
10126
+ }
9503
10127
  resolve14();
9504
10128
  }, 15e3);
9505
10129
  });
@@ -10097,8 +10721,8 @@ Use task_status("${taskId}") or task_output("${taskId}") to check progress.`
10097
10721
  }
10098
10722
  };
10099
10723
  }
10100
- function startTask(task, config, repoRoot, voice, stream) {
10101
- const projectCtx = buildProjectContext(repoRoot);
10724
+ function startTask(task, config, repoRoot, voice, stream, taskStores) {
10725
+ const projectCtx = buildProjectContext(repoRoot, taskStores?.contextStores);
10102
10726
  const dynamicContext = formatContextForPrompt(projectCtx);
10103
10727
  const backend = new OllamaAgenticBackend(config.backendUrl.replace(/\/$/, ""), config.model);
10104
10728
  const runner = new AgenticRunner(backend, {
@@ -10112,9 +10736,16 @@ function startTask(task, config, repoRoot, voice, stream) {
10112
10736
  streamEnabled: stream?.enabled ?? false
10113
10737
  });
10114
10738
  runner.registerTools(buildTools(repoRoot, config));
10739
+ const filesTouched = /* @__PURE__ */ new Set();
10115
10740
  runner.onEvent((event) => {
10116
10741
  switch (event.type) {
10117
10742
  case "tool_call":
10743
+ if (event.toolArgs?.path && typeof event.toolArgs.path === "string") {
10744
+ const name = event.toolName ?? "";
10745
+ if (name === "file_write" || name === "file_edit" || name === "batch_edit") {
10746
+ filesTouched.add(event.toolArgs.path);
10747
+ }
10748
+ }
10118
10749
  if (voice?.enabled) {
10119
10750
  const desc = describeToolCall(event.toolName ?? "unknown", event.toolArgs ?? {});
10120
10751
  renderVoiceText(desc);
@@ -10189,6 +10820,22 @@ function startTask(task, config, repoRoot, voice, stream) {
10189
10820
  });
10190
10821
  } catch {
10191
10822
  }
10823
+ if (taskStores?.taskMemoryStore) {
10824
+ try {
10825
+ taskStores.taskMemoryStore.insert({
10826
+ sessionId,
10827
+ repoRoot,
10828
+ goal: task.slice(0, 500),
10829
+ constraints: [],
10830
+ filesTouched: Array.from(filesTouched).slice(0, 50),
10831
+ patches: [],
10832
+ outcome: result.completed ? "success" : "partial",
10833
+ qualityScore: null,
10834
+ notes: result.summary.slice(0, 500)
10835
+ });
10836
+ } catch {
10837
+ }
10838
+ }
10192
10839
  });
10193
10840
  return { runner, promise };
10194
10841
  }
@@ -10200,6 +10847,17 @@ async function startInteractive(config, repoPath) {
10200
10847
  }
10201
10848
  initOaDirectory(repoRoot);
10202
10849
  const savedSettings = resolveSettings(repoRoot);
10850
+ let memoryDb = null;
10851
+ let taskMemoryStore = null;
10852
+ let failureStore = null;
10853
+ let contextStores;
10854
+ try {
10855
+ memoryDb = initDb(config.dbPath);
10856
+ taskMemoryStore = new TaskMemoryStore(memoryDb);
10857
+ failureStore = new FailureStore(memoryDb);
10858
+ contextStores = { taskMemoryStore, failureStore };
10859
+ } catch {
10860
+ }
10203
10861
  if (savedSettings.model)
10204
10862
  config = { ...config, model: savedSettings.model };
10205
10863
  if (savedSettings.backendUrl)
@@ -10330,6 +10988,12 @@ async function startInteractive(config, repoPath) {
10330
10988
  if (carousel.isRunning)
10331
10989
  carousel.stop();
10332
10990
  voiceEngine.dispose();
10991
+ if (memoryDb) {
10992
+ try {
10993
+ closeDb(memoryDb);
10994
+ } catch {
10995
+ }
10996
+ }
10333
10997
  rl.close();
10334
10998
  },
10335
10999
  async voiceToggle() {
@@ -10421,12 +11085,36 @@ ${c2.dim("Goodbye!")}
10421
11085
  const task = startTask(fullInput, currentConfig, repoRoot, voiceEngine, {
10422
11086
  enabled: streamEnabled,
10423
11087
  renderer: streamRenderer
11088
+ }, {
11089
+ contextStores,
11090
+ taskMemoryStore: taskMemoryStore ?? void 0,
11091
+ failureStore: failureStore ?? void 0
10424
11092
  });
10425
11093
  activeTask = task;
10426
11094
  showPrompt();
10427
11095
  await task.promise;
10428
11096
  } catch (err) {
10429
- renderError(err instanceof Error ? err.message : String(err));
11097
+ const errMsg = err instanceof Error ? err.message : String(err);
11098
+ renderError(errMsg);
11099
+ if (failureStore) {
11100
+ try {
11101
+ const { createHash: createHash2 } = await import("node:crypto");
11102
+ failureStore.insert({
11103
+ taskId: "",
11104
+ sessionId: `${Date.now()}`,
11105
+ repoRoot,
11106
+ failureType: "runtime-error",
11107
+ fingerprint: createHash2("sha256").update(errMsg.slice(0, 200)).digest("hex").slice(0, 16),
11108
+ filePath: null,
11109
+ errorMessage: errMsg.slice(0, 500),
11110
+ context: null,
11111
+ resolved: false,
11112
+ resolvedAt: null,
11113
+ notes: `Task: ${fullInput.slice(0, 200)}`
11114
+ });
11115
+ } catch {
11116
+ }
11117
+ }
10430
11118
  } finally {
10431
11119
  activeTask = null;
10432
11120
  }
@@ -10513,6 +11201,7 @@ var init_interactive = __esm({
10513
11201
  init_commands();
10514
11202
  init_setup();
10515
11203
  init_project_context();
11204
+ init_dist6();
10516
11205
  init_oa_directory();
10517
11206
  init_render();
10518
11207
  init_carousel();
@@ -10715,7 +11404,7 @@ var init_embeddings = __esm({
10715
11404
  });
10716
11405
 
10717
11406
  // packages/indexer/dist/index.js
10718
- var init_dist6 = __esm({
11407
+ var init_dist7 = __esm({
10719
11408
  "packages/indexer/dist/index.js"() {
10720
11409
  "use strict";
10721
11410
  init_codebase_indexer();
@@ -10822,7 +11511,7 @@ async function indexRepoCommand(opts, _config) {
10822
11511
  var init_index_repo = __esm({
10823
11512
  "packages/cli/dist/commands/index-repo.js"() {
10824
11513
  "use strict";
10825
- init_dist6();
11514
+ init_dist7();
10826
11515
  init_spinner();
10827
11516
  init_output();
10828
11517
  }
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.6",
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",