open-agents-ai 0.187.382 → 0.187.385

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 +301 -89
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -10955,11 +10955,11 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
10955
10955
  nodePaths.push(globalDir2);
10956
10956
  } catch {
10957
10957
  }
10958
- const { openSync: openSync2, closeSync: closeSync2 } = await import("node:fs");
10958
+ const { openSync: openSync3, closeSync: closeSync3 } = await import("node:fs");
10959
10959
  const daemonLogPath = join15(this.nexusDir, "daemon.log");
10960
10960
  const daemonErrPath = join15(this.nexusDir, "daemon.err");
10961
- const outFd = openSync2(daemonLogPath, "w");
10962
- const errFd = openSync2(daemonErrPath, "w");
10961
+ const outFd = openSync3(daemonLogPath, "w");
10962
+ const errFd = openSync3(daemonErrPath, "w");
10963
10963
  const child = spawn2("node", [daemonPath, this.nexusDir, agentName, agentType], {
10964
10964
  detached: true,
10965
10965
  stdio: ["ignore", outFd, errFd],
@@ -10968,11 +10968,11 @@ process.on('SIGINT', () => process.emit('SIGTERM'));
10968
10968
  });
10969
10969
  child.unref();
10970
10970
  try {
10971
- closeSync2(outFd);
10971
+ closeSync3(outFd);
10972
10972
  } catch {
10973
10973
  }
10974
10974
  try {
10975
- closeSync2(errFd);
10975
+ closeSync3(errFd);
10976
10976
  } catch {
10977
10977
  }
10978
10978
  const statusFile = join15(this.nexusDir, "status.json");
@@ -33929,11 +33929,11 @@ var init_storage = __esm({
33929
33929
  */
33930
33930
  async put(cid, block, options2 = {}) {
33931
33931
  options2?.signal?.throwIfAborted();
33932
- const releaseLock = await this.lock.readLock();
33932
+ const releaseLock2 = await this.lock.readLock();
33933
33933
  try {
33934
33934
  return await this.child.put(cid, block, options2);
33935
33935
  } finally {
33936
- releaseLock();
33936
+ releaseLock2();
33937
33937
  }
33938
33938
  }
33939
33939
  /**
@@ -33941,11 +33941,11 @@ var init_storage = __esm({
33941
33941
  */
33942
33942
  async *putMany(blocks, options2 = {}) {
33943
33943
  options2?.signal?.throwIfAborted();
33944
- const releaseLock = await this.lock.readLock();
33944
+ const releaseLock2 = await this.lock.readLock();
33945
33945
  try {
33946
33946
  yield* this.child.putMany(blocks, options2);
33947
33947
  } finally {
33948
- releaseLock();
33948
+ releaseLock2();
33949
33949
  }
33950
33950
  }
33951
33951
  /**
@@ -33953,11 +33953,11 @@ var init_storage = __esm({
33953
33953
  */
33954
33954
  async *get(cid, options2 = {}) {
33955
33955
  options2?.signal?.throwIfAborted();
33956
- const releaseLock = await this.lock.readLock();
33956
+ const releaseLock2 = await this.lock.readLock();
33957
33957
  try {
33958
33958
  yield* this.child.get(cid, options2);
33959
33959
  } finally {
33960
- releaseLock();
33960
+ releaseLock2();
33961
33961
  }
33962
33962
  }
33963
33963
  /**
@@ -33965,11 +33965,11 @@ var init_storage = __esm({
33965
33965
  */
33966
33966
  async *getMany(cids, options2 = {}) {
33967
33967
  options2?.signal?.throwIfAborted();
33968
- const releaseLock = await this.lock.readLock();
33968
+ const releaseLock2 = await this.lock.readLock();
33969
33969
  try {
33970
33970
  yield* this.child.getMany(cids, options2);
33971
33971
  } finally {
33972
- releaseLock();
33972
+ releaseLock2();
33973
33973
  }
33974
33974
  }
33975
33975
  /**
@@ -33977,14 +33977,14 @@ var init_storage = __esm({
33977
33977
  */
33978
33978
  async delete(cid, options2 = {}) {
33979
33979
  options2?.signal?.throwIfAborted();
33980
- const releaseLock = await this.lock.writeLock();
33980
+ const releaseLock2 = await this.lock.writeLock();
33981
33981
  try {
33982
33982
  if (await this.pins.isPinned(cid)) {
33983
33983
  throw new BlockPinnedError("Block was pinned - please unpin and try again");
33984
33984
  }
33985
33985
  await this.child.delete(cid, options2);
33986
33986
  } finally {
33987
- releaseLock();
33987
+ releaseLock2();
33988
33988
  }
33989
33989
  }
33990
33990
  /**
@@ -33992,7 +33992,7 @@ var init_storage = __esm({
33992
33992
  */
33993
33993
  async *deleteMany(cids, options2 = {}) {
33994
33994
  options2?.signal?.throwIfAborted();
33995
- const releaseLock = await this.lock.writeLock();
33995
+ const releaseLock2 = await this.lock.writeLock();
33996
33996
  try {
33997
33997
  const storage = this;
33998
33998
  yield* this.child.deleteMany((async function* () {
@@ -34004,25 +34004,25 @@ var init_storage = __esm({
34004
34004
  }
34005
34005
  })(), options2);
34006
34006
  } finally {
34007
- releaseLock();
34007
+ releaseLock2();
34008
34008
  }
34009
34009
  }
34010
34010
  async has(cid, options2 = {}) {
34011
34011
  options2?.signal?.throwIfAborted();
34012
- const releaseLock = await this.lock.readLock();
34012
+ const releaseLock2 = await this.lock.readLock();
34013
34013
  try {
34014
34014
  return await this.child.has(cid, options2);
34015
34015
  } finally {
34016
- releaseLock();
34016
+ releaseLock2();
34017
34017
  }
34018
34018
  }
34019
34019
  async *getAll(options2 = {}) {
34020
34020
  options2?.signal?.throwIfAborted();
34021
- const releaseLock = await this.lock.readLock();
34021
+ const releaseLock2 = await this.lock.readLock();
34022
34022
  try {
34023
34023
  yield* this.child.getAll(options2);
34024
34024
  } finally {
34025
- releaseLock();
34025
+ releaseLock2();
34026
34026
  }
34027
34027
  }
34028
34028
  createSession(root, options2) {
@@ -36869,7 +36869,7 @@ var init_src38 = __esm({
36869
36869
  this.events.dispatchEvent(new CustomEvent("stop", { detail: this }));
36870
36870
  }
36871
36871
  async gc(options2 = {}) {
36872
- const releaseLock = await this.blockstore.lock.writeLock();
36872
+ const releaseLock2 = await this.blockstore.lock.writeLock();
36873
36873
  try {
36874
36874
  const helia = this;
36875
36875
  const blockstore = this.blockstore.unwrap();
@@ -36889,7 +36889,7 @@ var init_src38 = __esm({
36889
36889
  }
36890
36890
  })()));
36891
36891
  } finally {
36892
- releaseLock();
36892
+ releaseLock2();
36893
36893
  }
36894
36894
  this.log("gc finished");
36895
36895
  }
@@ -529603,7 +529603,7 @@ __export(oa_directory_exports, {
529603
529603
  writeIndexData: () => writeIndexData,
529604
529604
  writeIndexMeta: () => writeIndexMeta
529605
529605
  });
529606
- import { existsSync as existsSync57, mkdirSync as mkdirSync32, readFileSync as readFileSync44, writeFileSync as writeFileSync29, readdirSync as readdirSync14, statSync as statSync17, unlinkSync as unlinkSync14 } from "node:fs";
529606
+ import { existsSync as existsSync57, mkdirSync as mkdirSync32, readFileSync as readFileSync44, writeFileSync as writeFileSync29, readdirSync as readdirSync14, statSync as statSync17, unlinkSync as unlinkSync14, openSync as openSync2, closeSync as closeSync2 } from "node:fs";
529607
529607
  import { join as join75, relative as relative6, basename as basename13 } from "node:path";
529608
529608
  import { homedir as homedir25 } from "node:os";
529609
529609
  function initOaDirectory(repoRoot) {
@@ -529838,33 +529838,204 @@ function loadPendingTask(repoRoot) {
529838
529838
  return null;
529839
529839
  }
529840
529840
  }
529841
+ function acquireLock(lockPath) {
529842
+ const startTime = Date.now();
529843
+ const pid = process.pid;
529844
+ for (let attempt = 0; attempt < LOCK_RETRY_MAX; attempt++) {
529845
+ try {
529846
+ const fd = openSync2(lockPath, "wx");
529847
+ const lockData = JSON.stringify({ pid, acquiredAt: Date.now() });
529848
+ writeFileSync29(fd, lockData);
529849
+ closeSync2(fd);
529850
+ return true;
529851
+ } catch (err) {
529852
+ if (existsSync57(lockPath)) {
529853
+ try {
529854
+ const lockContent = readFileSync44(lockPath, "utf-8");
529855
+ const lock = JSON.parse(lockContent);
529856
+ const lockAge = Date.now() - lock.acquiredAt;
529857
+ if (lockAge > LOCK_TIMEOUT_MS) {
529858
+ try {
529859
+ unlinkSync14(lockPath);
529860
+ } catch {
529861
+ }
529862
+ }
529863
+ } catch {
529864
+ try {
529865
+ unlinkSync14(lockPath);
529866
+ } catch {
529867
+ }
529868
+ }
529869
+ }
529870
+ if (Date.now() - startTime < LOCK_TIMEOUT_MS) {
529871
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, LOCK_RETRY_MS);
529872
+ }
529873
+ }
529874
+ }
529875
+ return false;
529876
+ }
529877
+ function releaseLock(lockPath) {
529878
+ try {
529879
+ if (existsSync57(lockPath)) {
529880
+ const lockContent = readFileSync44(lockPath, "utf-8");
529881
+ const lock = JSON.parse(lockContent);
529882
+ if (lock.pid === process.pid) {
529883
+ unlinkSync14(lockPath);
529884
+ }
529885
+ }
529886
+ } catch {
529887
+ }
529888
+ }
529889
+ function normalizeSessionText(raw, maxLen = 500) {
529890
+ if (!raw) return "";
529891
+ return String(raw).replace(/\s+/g, " ").trim().slice(0, maxLen);
529892
+ }
529893
+ function normalizeList(items, maxItems) {
529894
+ if (!items || items.length === 0) return void 0;
529895
+ const seen = /* @__PURE__ */ new Set();
529896
+ const out = [];
529897
+ for (const item of items) {
529898
+ const clean3 = normalizeSessionText(item, 300);
529899
+ if (!clean3 || seen.has(clean3)) continue;
529900
+ seen.add(clean3);
529901
+ out.push(clean3);
529902
+ if (out.length >= maxItems) break;
529903
+ }
529904
+ return out.length > 0 ? out : void 0;
529905
+ }
529906
+ function normalizeSessionContextEntry(entry) {
529907
+ const normalized = {
529908
+ ...entry,
529909
+ savedAt: entry.savedAt || (/* @__PURE__ */ new Date()).toISOString(),
529910
+ task: cleanPromptForDiary(entry.task).slice(0, 500),
529911
+ summary: normalizeSessionText(entry.summary, 500),
529912
+ filesModified: normalizeList(entry.filesModified, 30) ?? [],
529913
+ toolCalls: Math.max(0, entry.toolCalls ?? 0),
529914
+ completed: Boolean(entry.completed),
529915
+ model: normalizeSessionText(entry.model, 160)
529916
+ };
529917
+ const toolsUsed = normalizeList(entry.toolsUsed, 12);
529918
+ if (toolsUsed) normalized.toolsUsed = toolsUsed;
529919
+ const provenance = normalizeSessionText(entry.provenance, 300);
529920
+ if (provenance) normalized.provenance = provenance;
529921
+ const assistantResponse = normalizeSessionText(entry.assistantResponse, 1200);
529922
+ if (assistantResponse) normalized.assistantResponse = assistantResponse;
529923
+ if (entry.source) normalized.source = entry.source;
529924
+ return normalized;
529925
+ }
529926
+ function sameStringArray(a2, b) {
529927
+ const left = a2 ?? [];
529928
+ const right = b ?? [];
529929
+ if (left.length !== right.length) return false;
529930
+ for (let i2 = 0; i2 < left.length; i2++) {
529931
+ if (left[i2] !== right[i2]) return false;
529932
+ }
529933
+ return true;
529934
+ }
529935
+ function lastMatchingIndex(items, predicate) {
529936
+ for (let i2 = items.length - 1; i2 >= 0; i2--) {
529937
+ if (predicate(items[i2])) return i2;
529938
+ }
529939
+ return -1;
529940
+ }
529941
+ function sameTask(a2, b) {
529942
+ return cleanPromptForDiary(a2.task) === cleanPromptForDiary(b.task);
529943
+ }
529944
+ function parseIsoMs(value2) {
529945
+ if (!value2) return null;
529946
+ const ms = Date.parse(value2);
529947
+ return Number.isFinite(ms) ? ms : null;
529948
+ }
529949
+ function isWithinReplaceWindow(a2, b) {
529950
+ const left = parseIsoMs(a2.savedAt);
529951
+ const right = parseIsoMs(b.savedAt);
529952
+ if (left == null || right == null) return true;
529953
+ return Math.abs(right - left) <= SAME_TASK_REPLACE_WINDOW_MS;
529954
+ }
529955
+ function isExactSessionDuplicate(a2, b) {
529956
+ return sameTask(a2, b) && a2.summary === b.summary && (a2.assistantResponse ?? "") === (b.assistantResponse ?? "") && sameStringArray(a2.filesModified, b.filesModified) && sameStringArray(a2.toolsUsed, b.toolsUsed) && (a2.provenance ?? "") === (b.provenance ?? "") && a2.toolCalls === b.toolCalls && a2.completed === b.completed && a2.model === b.model;
529957
+ }
529958
+ function mergeSessionContextEntry(previous, incoming) {
529959
+ return {
529960
+ ...previous,
529961
+ ...incoming,
529962
+ savedAt: incoming.savedAt || previous.savedAt,
529963
+ task: incoming.task || previous.task,
529964
+ summary: incoming.summary || previous.summary,
529965
+ assistantResponse: incoming.assistantResponse || previous.assistantResponse || incoming.summary || previous.summary,
529966
+ filesModified: incoming.filesModified.length > 0 ? incoming.filesModified : previous.filesModified,
529967
+ toolCalls: Math.max(previous.toolCalls ?? 0, incoming.toolCalls ?? 0),
529968
+ toolsUsed: incoming.toolsUsed && incoming.toolsUsed.length > 0 ? incoming.toolsUsed : previous.toolsUsed,
529969
+ provenance: incoming.provenance || previous.provenance,
529970
+ completed: previous.completed || incoming.completed,
529971
+ model: incoming.model || previous.model,
529972
+ source: incoming.source || previous.source
529973
+ };
529974
+ }
529841
529975
  function saveSessionContext(repoRoot, entry) {
529842
529976
  const contextDir = join75(repoRoot, OA_DIR, "context");
529843
529977
  mkdirSync32(contextDir, { recursive: true });
529844
529978
  const filePath = join75(contextDir, CONTEXT_SAVE_FILE);
529845
- let ctx3;
529979
+ const lockPath = join75(contextDir, CONTEXT_SAVE_FILE + ".lock");
529980
+ const locked = acquireLock(lockPath);
529981
+ if (!locked) {
529982
+ console.warn("[saveSessionContext] Could not acquire lock, proceeding without mutex");
529983
+ }
529846
529984
  try {
529847
- if (existsSync57(filePath)) {
529848
- ctx3 = JSON.parse(readFileSync44(filePath, "utf-8"));
529849
- } else {
529985
+ let ctx3;
529986
+ try {
529987
+ if (existsSync57(filePath)) {
529988
+ ctx3 = JSON.parse(readFileSync44(filePath, "utf-8"));
529989
+ } else {
529990
+ ctx3 = { entries: [], maxEntries: MAX_CONTEXT_ENTRIES, updatedAt: "" };
529991
+ }
529992
+ } catch {
529850
529993
  ctx3 = { entries: [], maxEntries: MAX_CONTEXT_ENTRIES, updatedAt: "" };
529851
529994
  }
529852
- } catch {
529853
- ctx3 = { entries: [], maxEntries: MAX_CONTEXT_ENTRIES, updatedAt: "" };
529854
- }
529855
- ctx3.entries.push(entry);
529856
- if (ctx3.entries.length > ctx3.maxEntries) {
529857
- ctx3.entries = ctx3.entries.slice(-ctx3.maxEntries);
529858
- }
529859
- ctx3.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
529860
- writeFileSync29(filePath, JSON.stringify(ctx3, null, 2) + "\n", "utf-8");
529861
- try {
529862
- writeFileSync29(
529863
- join75(contextDir, "session-diary.md"),
529864
- renderSessionDiary(ctx3.entries.slice(-10)),
529865
- "utf-8"
529995
+ const normalizedEntry = normalizeSessionContextEntry(entry);
529996
+ const exactIndex = lastMatchingIndex(
529997
+ ctx3.entries,
529998
+ (existing) => isExactSessionDuplicate(normalizeSessionContextEntry(existing), normalizedEntry)
529866
529999
  );
529867
- } catch {
530000
+ if (exactIndex >= 0) {
530001
+ ctx3.entries[exactIndex] = mergeSessionContextEntry(ctx3.entries[exactIndex], normalizedEntry);
530002
+ } else {
530003
+ const relatedIndex = lastMatchingIndex(ctx3.entries, (existing) => {
530004
+ const normalizedExisting = normalizeSessionContextEntry(existing);
530005
+ return sameTask(normalizedExisting, normalizedEntry) && isWithinReplaceWindow(normalizedExisting, normalizedEntry);
530006
+ });
530007
+ if (relatedIndex >= 0) {
530008
+ const previous = normalizeSessionContextEntry(ctx3.entries[relatedIndex]);
530009
+ const shouldDropPartialResave = previous.completed && !normalizedEntry.completed;
530010
+ const shouldReplaceExisting = !previous.completed || normalizedEntry.source === "manual" || previous.completed && normalizedEntry.completed;
530011
+ if (!shouldDropPartialResave) {
530012
+ if (shouldReplaceExisting) {
530013
+ ctx3.entries[relatedIndex] = mergeSessionContextEntry(ctx3.entries[relatedIndex], normalizedEntry);
530014
+ } else {
530015
+ ctx3.entries.push(normalizedEntry);
530016
+ }
530017
+ }
530018
+ } else {
530019
+ ctx3.entries.push(normalizedEntry);
530020
+ }
530021
+ }
530022
+ if (ctx3.entries.length > ctx3.maxEntries) {
530023
+ ctx3.entries = ctx3.entries.slice(-ctx3.maxEntries);
530024
+ }
530025
+ ctx3.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
530026
+ writeFileSync29(filePath, JSON.stringify(ctx3, null, 2) + "\n", "utf-8");
530027
+ try {
530028
+ writeFileSync29(
530029
+ join75(contextDir, "session-diary.md"),
530030
+ renderSessionDiary(ctx3.entries.slice(-10)),
530031
+ "utf-8"
530032
+ );
530033
+ } catch {
530034
+ }
530035
+ } finally {
530036
+ if (locked) {
530037
+ releaseLock(lockPath);
530038
+ }
529868
530039
  }
529869
530040
  }
529870
530041
  function cleanPromptForDiary(raw) {
@@ -529912,6 +530083,9 @@ function renderSessionDiary(entries) {
529912
530083
  if (e2.provenance) {
529913
530084
  lines.push(`- **Provenance:** ${e2.provenance}`);
529914
530085
  }
530086
+ if (e2.assistantResponse) {
530087
+ lines.push(`- **Assistant:** ${e2.assistantResponse.replace(/\s+/g, " ").trim().slice(0, 280)}`);
530088
+ }
529915
530089
  if (e2.summary) {
529916
530090
  const summary = e2.summary.replace(/\s+/g, " ").trim().slice(0, 280);
529917
530091
  lines.push(`- **Summary:** ${summary}`);
@@ -529935,22 +530109,35 @@ function buildContextRestorePrompt(repoRoot) {
529935
530109
  const ctx3 = loadSessionContext(repoRoot);
529936
530110
  if (!ctx3 || ctx3.entries.length === 0) return null;
529937
530111
  const recent = ctx3.entries.slice(-5);
529938
- const lines = recent.map((e2) => {
530112
+ const chronology = recent.map((e2) => {
529939
530113
  const status = e2.completed ? "done" : "partial";
529940
- const summary = e2.summary ? e2.summary.slice(0, 120) : e2.task.slice(0, 120);
530114
+ const summary = normalizeSessionText(e2.assistantResponse || e2.summary || e2.task, 140);
529941
530115
  const tools = e2.toolsUsed && e2.toolsUsed.length > 0 ? ` | tools: ${e2.toolsUsed.slice(0, 4).join(", ")}` : "";
529942
530116
  const files = e2.filesModified && e2.filesModified.length > 0 ? ` | files: ${e2.filesModified.slice(0, 3).join(", ")}` : "";
529943
530117
  return `[${status}] ${summary}${tools}${files}`;
529944
530118
  });
529945
530119
  const last2 = ctx3.entries[ctx3.entries.length - 1];
530120
+ const lastCompleted = [...ctx3.entries].reverse().find((entry) => entry.completed);
530121
+ const latestCompleted = lastCompleted ? `Latest completed task: ${normalizeSessionText(lastCompleted.assistantResponse || lastCompleted.summary || lastCompleted.task, 180)}` : "";
530122
+ const recentDialogue = recent.slice(-3).map((entry) => {
530123
+ const assistant = normalizeSessionText(entry.assistantResponse || entry.summary, 320) || "(no assistant reply captured)";
530124
+ const user = cleanPromptForDiary(entry.task).slice(0, 220);
530125
+ return `User: ${user}
530126
+ Assistant: ${assistant}`;
530127
+ }).join("\n\n");
529946
530128
  const prov = last2.provenance ? `
529947
530129
  Provenance: ${last2.provenance} (file_read to expand)` : "";
529948
530130
  const kg = `
529949
530131
  KG summary: .oa/context/kg-summary-latest.md (file_read to expand)`;
529950
530132
  return `<session-recap>
529951
- Previous work in this project:
529952
- ${lines.join("\n")}
529953
- Do not repeat completed work.${prov}${kg}
530133
+ Project chronology (older to newer):
530134
+ ${chronology.join("\n")}
530135
+ ` + (latestCompleted ? `
530136
+ ${latestCompleted}
530137
+ ` : "\n") + `
530138
+ Most recent exchanges (older to newer):
530139
+ ${recentDialogue}
530140
+ Continue from the latest exchange and do not repeat completed work.${prov}${kg}
529954
530141
  </session-recap>`;
529955
530142
  }
529956
530143
  function getLastTaskSummary(repoRoot) {
@@ -530236,7 +530423,7 @@ function deleteUsageRecord(kind, value2, repoRoot) {
530236
530423
  remove(join75(repoRoot, OA_DIR, USAGE_HISTORY_FILE));
530237
530424
  }
530238
530425
  }
530239
- var OA_DIR, SUBDIRS, CONTEXT_FILES, PENDING_TASK_FILE, CONTEXT_SAVE_FILE, MAX_CONTEXT_ENTRIES, SESSIONS_DIR, SESSIONS_INDEX, SKIP_DIRS2, HOME_SKIP_DIRS, USAGE_HISTORY_FILE, MAX_HISTORY_RECORDS;
530426
+ var OA_DIR, SUBDIRS, CONTEXT_FILES, PENDING_TASK_FILE, CONTEXT_SAVE_FILE, MAX_CONTEXT_ENTRIES, SAME_TASK_REPLACE_WINDOW_MS, LOCK_TIMEOUT_MS, LOCK_RETRY_MS, LOCK_RETRY_MAX, SESSIONS_DIR, SESSIONS_INDEX, SKIP_DIRS2, HOME_SKIP_DIRS, USAGE_HISTORY_FILE, MAX_HISTORY_RECORDS;
530240
530427
  var init_oa_directory = __esm({
530241
530428
  "packages/cli/src/tui/oa-directory.ts"() {
530242
530429
  "use strict";
@@ -530255,6 +530442,10 @@ var init_oa_directory = __esm({
530255
530442
  PENDING_TASK_FILE = "pending-task.json";
530256
530443
  CONTEXT_SAVE_FILE = "session-context.json";
530257
530444
  MAX_CONTEXT_ENTRIES = 20;
530445
+ SAME_TASK_REPLACE_WINDOW_MS = 12 * 60 * 60 * 1e3;
530446
+ LOCK_TIMEOUT_MS = 5e3;
530447
+ LOCK_RETRY_MS = 50;
530448
+ LOCK_RETRY_MAX = 100;
530258
530449
  SESSIONS_DIR = "sessions";
530259
530450
  SESSIONS_INDEX = "sessions-index.json";
530260
530451
  SKIP_DIRS2 = /* @__PURE__ */ new Set([
@@ -532003,6 +532194,9 @@ var init_status_bar = __esm({
532003
532194
  // Since we're in alternate screen buffer, there's no native scrollback.
532004
532195
  // NOTE: _contentLines is now a reference to the ACTIVE view's buffer.
532005
532196
  _contentLines = [];
532197
+ /** Last buffered visible-line fingerprint — suppresses accidental double inserts */
532198
+ _lastBufferedFingerprint = "";
532199
+ _lastBufferedAt = 0;
532006
532200
  _contentScrollOffset = 0;
532007
532201
  // 0 = live (bottom), >0 = scrolled back
532008
532202
  _contentMaxLines = 1e4;
@@ -532924,6 +533118,8 @@ var init_status_bar = __esm({
532924
533118
  this._contentLines.splice(0, this._contentLines.length);
532925
533119
  this._contentScrollOffset = 0;
532926
533120
  this._inProgressLine = "";
533121
+ this._lastBufferedFingerprint = "";
533122
+ this._lastBufferedAt = 0;
532927
533123
  this._autoScroll = true;
532928
533124
  this.fillContentArea();
532929
533125
  this.renderFooterAndPositionInput();
@@ -533615,9 +533811,10 @@ var init_status_bar = __esm({
533615
533811
  self2._inProgressLine = combined.slice(lastNl + 1);
533616
533812
  const completeLines = completed.split("\n");
533617
533813
  for (const line of completeLines) {
533618
- const visible = line.replace(/\x1B\[[0-9;]*[A-Za-z]/g, "");
533619
- if (line.length === 0 || visible.length > 0) {
533620
- self2.bufferContentLine(line);
533814
+ const sanitized = self2.sanitizeBufferedContentLine(line);
533815
+ const visible = stripAnsi(sanitized);
533816
+ if (sanitized.length === 0 || visible.length > 0) {
533817
+ self2.bufferContentLine(sanitized);
533621
533818
  }
533622
533819
  }
533623
533820
  }
@@ -533653,8 +533850,9 @@ ${CONTENT_BG_SEQ}`);
533653
533850
  this.writeDepth = Math.max(0, this.writeDepth - 1);
533654
533851
  if (this.writeDepth === 0) {
533655
533852
  if (this._inProgressLine.length > 0) {
533656
- const visible = this._inProgressLine.replace(/\x1B\[[0-9;]*[A-Za-z]/g, "");
533657
- if (visible.length > 0) this.bufferContentLine(this._inProgressLine);
533853
+ const sanitized = this.sanitizeBufferedContentLine(this._inProgressLine);
533854
+ const visible = stripAnsi(sanitized);
533855
+ if (visible.length > 0) this.bufferContentLine(sanitized);
533658
533856
  this._inProgressLine = "";
533659
533857
  }
533660
533858
  this._bufferContent = false;
@@ -533707,7 +533905,15 @@ ${CONTENT_BG_SEQ}`);
533707
533905
  this.renderFooterAndPositionInput();
533708
533906
  }
533709
533907
  bufferContentLine(line) {
533710
- this._contentLines.push(line);
533908
+ const sanitized = this.sanitizeBufferedContentLine(line);
533909
+ const fingerprint = stripAnsi(sanitized).trim();
533910
+ const now = Date.now();
533911
+ if (fingerprint && fingerprint === this._lastBufferedFingerprint && now - this._lastBufferedAt < 50) {
533912
+ return;
533913
+ }
533914
+ this._lastBufferedFingerprint = fingerprint;
533915
+ this._lastBufferedAt = now;
533916
+ this._contentLines.push(sanitized);
533711
533917
  if (this._contentLines.length > this._contentMaxLines) {
533712
533918
  this._contentLines.splice(0, this._contentLines.length - this._contentMaxLines);
533713
533919
  if (this._contentScrollOffset > 0) {
@@ -533719,6 +533925,10 @@ ${CONTENT_BG_SEQ}`);
533719
533925
  }
533720
533926
  if (this._autoScroll && !this._mouseSelecting) this._contentScrollOffset = 0;
533721
533927
  }
533928
+ /** Keep SGR styling, drop replay-unsafe terminal control sequences from scrollback storage. */
533929
+ sanitizeBufferedContentLine(line) {
533930
+ return line.replace(/\r/g, "").replace(/\x1B\][^\x07]*(?:\x07|\x1B\\)/g, "").replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, (seq) => seq.endsWith("m") ? seq : "");
533931
+ }
533722
533932
  /**
533723
533933
  * Remove the last N lines from the content scrollback buffer and repaint.
533724
533934
  * Used by Esc-to-recall to erase the just-rendered user prompt.
@@ -556995,7 +557205,6 @@ function loadMemoryContext(repoRoot) {
556995
557205
  }
556996
557206
  function loadSessionHistory2(repoRoot) {
556997
557207
  const sessions2 = loadRecentSessions(repoRoot, 5);
556998
- if (sessions2.length === 0) return "";
556999
557208
  const lines = ["Recent tasks in this project:"];
557000
557209
  for (const s2 of sessions2) {
557001
557210
  if (!s2.startedAt || !s2.task) continue;
@@ -557008,7 +557217,20 @@ function loadSessionHistory2(repoRoot) {
557008
557217
  lines.push(` Summary: ${cleanSummary.slice(0, 120)}`);
557009
557218
  }
557010
557219
  }
557011
- return lines.join("\n");
557220
+ const sessionCtx = loadSessionContext(repoRoot);
557221
+ const exchanges = sessionCtx?.entries.slice(-2) ?? [];
557222
+ if (exchanges.length > 0) {
557223
+ if (lines.length > 1) lines.push("");
557224
+ lines.push("Most recent user/assistant chronology:");
557225
+ for (const entry of exchanges) {
557226
+ const user = cleanForStorage(entry.task) || entry.task;
557227
+ const assistant = cleanForStorage(entry.assistantResponse || entry.summary) || entry.assistantResponse || entry.summary;
557228
+ if (!assistant) continue;
557229
+ lines.push(`- User: ${user.slice(0, 120)}`);
557230
+ lines.push(` Assistant: ${assistant.slice(0, 160)}`);
557231
+ }
557232
+ }
557233
+ return lines.length > 1 ? lines.join("\n") : "";
557012
557234
  }
557013
557235
  function getEnvironment(repoRoot) {
557014
557236
  return [
@@ -564282,12 +564504,14 @@ var init_mouse_filter = __esm({
564282
564504
  onScroll = null;
564283
564505
  onActivity = null;
564284
564506
  onPointer = null;
564507
+ onKeyboard = null;
564285
564508
  flushTimer = null;
564286
- constructor(scrollHandler, activityHandler, pointerHandler) {
564509
+ constructor(scrollHandler, activityHandler, pointerHandler, keyboardHandler) {
564287
564510
  super();
564288
564511
  this.onScroll = scrollHandler;
564289
564512
  this.onActivity = activityHandler ?? null;
564290
564513
  this.onPointer = pointerHandler ?? null;
564514
+ this.onKeyboard = keyboardHandler ?? null;
564291
564515
  }
564292
564516
  _transform(chunk, _encoding, callback) {
564293
564517
  this.buffer += chunk.toString();
@@ -564309,7 +564533,9 @@ var init_mouse_filter = __esm({
564309
564533
  else if ((btn === 65 || btn === 97) && this.onScroll) this.onScroll("down", 3, row);
564310
564534
  else if (this.onPointer) {
564311
564535
  const hasShift = (btn & 4) !== 0 && btn < 32;
564312
- if (btn === 2 || hasShift) {
564536
+ if (btn === 2) {
564537
+ } else if (hasShift) {
564538
+ process.stdout.write("\x1B[?1000l\x1B[?1006l");
564313
564539
  } else if ((btn === 0 || btn === 1) && suffix === "M") {
564314
564540
  this.onPointer("press", col, row);
564315
564541
  } else if (btn >= 32 && btn <= 35 && suffix === "M") {
@@ -564345,6 +564571,7 @@ var init_mouse_filter = __esm({
564345
564571
  }
564346
564572
  this.buffer = this.buffer.slice(i2);
564347
564573
  if (output.length > 0) {
564574
+ if (this.onKeyboard) this.onKeyboard();
564348
564575
  this.push(output);
564349
564576
  }
564350
564577
  if (this.buffer.length > 0) {
@@ -566858,11 +567085,13 @@ async function handleContextSave(ctx3) {
566858
567085
  savedAt: (/* @__PURE__ */ new Date()).toISOString(),
566859
567086
  task: typeof body?.task === "string" ? body.task : typeof body?.prompt === "string" ? body.prompt : typeof body?.task_id === "string" ? body.task_id : `manual-${Date.now()}`,
566860
567087
  summary: typeof body?.summary === "string" ? body.summary : "",
567088
+ assistantResponse: typeof body?.assistant_response === "string" ? body.assistant_response : "",
566861
567089
  filesModified: Array.isArray(body?.files_modified) ? body.files_modified : [],
566862
567090
  toolCalls: typeof body?.tool_calls === "number" ? body.tool_calls : 0,
566863
567091
  completed: body?.completed !== false,
566864
567092
  // default true
566865
- model: typeof body?.model === "string" ? body.model : ""
567093
+ model: typeof body?.model === "string" ? body.model : "",
567094
+ source: "api"
566866
567095
  };
566867
567096
  saveSessionContext(repoRoot, entry);
566868
567097
  publishEvent("session.created", { task: entry.task.slice(0, 80) }, {
@@ -579633,7 +579862,6 @@ function startTask(task, config, repoRoot, voice, stream, taskStores, bruteForce
579633
579862
  const modelTier = getModelTier(config.model);
579634
579863
  const projectCtx = buildProjectContext(repoRoot, taskStores?.contextStores);
579635
579864
  let dynamicContext = formatContextForPrompt(projectCtx, modelTier);
579636
- let _autoJumpedThisStream = false;
579637
579865
  const environmentProvider = () => {
579638
579866
  try {
579639
579867
  let env2 = formatSnapshotForContext(collectSnapshot(repoRoot));
@@ -580247,6 +580475,7 @@ ${entry.fullContent}`
580247
580475
  let toolCallStartMs = 0;
580248
580476
  let streamStartMs = 0;
580249
580477
  let streamTextBuffer = "";
580478
+ let lastAssistantText = "";
580250
580479
  let lastProvenancePath = null;
580251
580480
  let showLittleman = false;
580252
580481
  const littlemanBuffer = [];
@@ -580283,25 +580512,9 @@ ${entry.fullContent}`
580283
580512
  }
580284
580513
  if (statusBar?.isActive) {
580285
580514
  statusBar.beginContentWrite();
580286
- const alreadyBuffered = process.stdout.write.__oa_buffer_lines === true;
580287
- let postInterceptWrite = null;
580288
- if (!alreadyBuffered) {
580289
- postInterceptWrite = process.stdout.write;
580290
- const boundWrite = postInterceptWrite.bind(process.stdout);
580291
- process.stdout.write = ((chunk, ...args) => {
580292
- const text = typeof chunk === "string" ? chunk : new TextDecoder().decode(chunk);
580293
- for (const line of text.split("\n")) {
580294
- if (line.replace(/\x1B\[[0-9;]*[A-Za-z]/g, "").length > 0) {
580295
- statusBar.bufferContentLine(line);
580296
- }
580297
- }
580298
- return boundWrite(chunk, ...args);
580299
- });
580300
- }
580301
580515
  try {
580302
580516
  fn();
580303
580517
  } finally {
580304
- if (postInterceptWrite) process.stdout.write = postInterceptWrite;
580305
580518
  statusBar.endContentWrite();
580306
580519
  }
580307
580520
  } else {
@@ -580444,7 +580657,6 @@ ${entry.fullContent}`
580444
580657
  case "stream_start":
580445
580658
  streamStartMs = Date.now();
580446
580659
  streamTextBuffer = "";
580447
- _autoJumpedThisStream = false;
580448
580660
  if (!isNeovimActive()) {
580449
580661
  if (stream?.enabled) {
580450
580662
  if (statusBar?.isActive) statusBar.beginContentWrite();
@@ -580463,15 +580675,6 @@ ${entry.fullContent}`
580463
580675
  writeToNeovimOutput(event.content);
580464
580676
  }
580465
580677
  } else if (stream?.enabled) {
580466
- if (statusBar?.isActive && statusBar.isScrolledBack && !_autoJumpedThisStream) {
580467
- if ((event.streamKind ?? "content") === "thinking") {
580468
- try {
580469
- statusBar.jumpToLive();
580470
- } catch {
580471
- }
580472
- _autoJumpedThisStream = true;
580473
- }
580474
- }
580475
580678
  stream.renderer.write(
580476
580679
  event.content ?? "",
580477
580680
  event.streamKind ?? "content"
@@ -580584,6 +580787,10 @@ ${entry.fullContent}`
580584
580787
  if (sudoCallback) sudoCallback(event.content ?? "");
580585
580788
  break;
580586
580789
  case "assistant_text":
580790
+ if (event.content) {
580791
+ const cleanAssistantText = cleanForStorage(event.content).trim();
580792
+ if (cleanAssistantText) lastAssistantText = cleanAssistantText;
580793
+ }
580587
580794
  if (_apiCallbacks?.onAssistantText && event.content) {
580588
580795
  _apiCallbacks.onAssistantText(event.content, event.turn ?? 0);
580589
580796
  }
@@ -580792,14 +580999,17 @@ When done, either call task_complete with your answer, or use FINAL_VAR(variable
580792
580999
  } catch {
580793
581000
  }
580794
581001
  try {
581002
+ const assistantResponse = cleanForStorage(lastAssistantText || result.summary).slice(0, 1200);
580795
581003
  saveSessionContext(repoRoot, {
580796
581004
  savedAt: (/* @__PURE__ */ new Date()).toISOString(),
580797
581005
  task: cleanPromptForDiary(task).slice(0, 500),
580798
581006
  summary: result.summary.slice(0, 500),
581007
+ assistantResponse,
580799
581008
  filesModified: Array.from(filesTouched).slice(0, 30),
580800
581009
  toolCalls: result.toolCalls,
580801
581010
  completed: result.completed,
580802
- model: config.model
581011
+ model: config.model,
581012
+ source: "task_complete"
580803
581013
  });
580804
581014
  } catch {
580805
581015
  }
@@ -581562,7 +581772,6 @@ ${opts.systemPromptAddition}` : `Working directory: ${repoRoot}`;
581562
581772
  let adminSessionKey = null;
581563
581773
  const callSubAgents = /* @__PURE__ */ new Map();
581564
581774
  const streamRenderer = new StreamRenderer();
581565
- let _autoJumpedThisStream = false;
581566
581775
  if (savedSettings.voice) {
581567
581776
  if (savedSettings.voiceModel) {
581568
581777
  voiceEngine.modelId = savedSettings.voiceModel;
@@ -581813,6 +582022,8 @@ Rationale: ${proposal.rationale}${provenanceNote}`;
581813
582022
  statusBar.scheduleMouseIdle();
581814
582023
  }, (type, col, row) => {
581815
582024
  statusBar.handlePointerEvent(type, col, row);
582025
+ }, () => {
582026
+ statusBar.enableMouseTracking();
581816
582027
  });
581817
582028
  process.stdin.pipe(mouseFilter);
581818
582029
  process.stdin.on("error", (err) => {
@@ -583891,6 +584102,7 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
583891
584102
  summary: `Manual context save. ${sessionToolCallCount} tool calls, ${sessionFilesTouched.length} files modified.`,
583892
584103
  filesModified: [...sessionFilesTouched],
583893
584104
  toolCalls: sessionToolCallCount,
584105
+ source: "manual",
583894
584106
  completed: false,
583895
584107
  model: currentConfig.model
583896
584108
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.187.382",
3
+ "version": "0.187.385",
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",