agenr 0.9.78 → 0.9.80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.9.80] - 2026-03-08
4
+
5
+ ### Fixed
6
+
7
+ - Added startup-only `SQLITE_BUSY` retry/backoff for OpenClaw session-start browse and memory-index reads, reducing transient lock-induced degradation immediately after handoff recovery.
8
+ - Session-start browse now logs explicit retry and unavailable states instead of collapsing lock failures into the ordinary `browse returned 0 entries` path.
9
+ - Preserved existing startup fail-open rendering behavior by continuing to omit unavailable browse or memory-index sections while still completing session-start with the remaining recovered context.
10
+ - Added focused regression coverage for strict startup browse execution, compatibility preservation in `runRecall()`, and session-start retry/degradation logging for browse and memory-index lock contention.
11
+
12
+ ## [0.9.79] - 2026-03-08
13
+
14
+ ### Fixed
15
+
16
+ - Fixed OpenClaw session-start memory index observability so startup logs now distinguish successful empty loads from timeout, invalid-response, and error states instead of collapsing them all into `0 projects`.
17
+ - Fixed the OpenClaw session-start core recall debug log to report configured `coreProjects` truthfully rather than mislabeling config length as "active projects".
18
+ - Fixed OpenClaw session-start fallback recovery for newer TUI session keys such as `agent:main:tui-<uuid>` by allowing degraded same-family TUI predecessor acceptance when the current session identity resolves `family=tui` but cannot recover a stable lane.
19
+ - Preserved strict lane matching for explicit predecessors and known-lane fallback cases, while keeping cross-family fallback candidates ineligible.
20
+ - Added regression coverage for memory-index load result typing, session-start memory-index availability logging, markdown omission on unavailable index states, truthful core-project log wording, unknown-lane TUI fallback acceptance, alternate same-family TUI lane acceptance, cross-family rejection, and the new-key debug logging path.
21
+
3
22
  ## [0.9.78] - 2026-03-08
4
23
 
5
24
  ### Fixed
@@ -635,13 +635,15 @@ __export(recall_exports, {
635
635
  resolveAgenrPath: () => resolveAgenrPath,
636
636
  resolveBudget: () => resolveBudget,
637
637
  runMemoryIndex: () => runMemoryIndex,
638
- runRecall: () => runRecall
638
+ runRecall: () => runRecall,
639
+ runRecallStrict: () => runRecallStrict
639
640
  });
640
641
  import path from "path";
641
642
  import { fileURLToPath } from "url";
642
643
 
643
644
  // src/openclaw-plugin/memory-index.ts
644
645
  var MEMORY_INDEX_TIMEOUT_MS = 1e4;
646
+ var MEMORY_INDEX_TIMEOUT = /* @__PURE__ */ Symbol("memory-index-timeout");
645
647
  function normalizeDate(value) {
646
648
  const raw = typeof value === "string" ? value.trim() : "";
647
649
  if (!raw) {
@@ -693,9 +695,16 @@ function parseMemoryIndex(value) {
693
695
  }
694
696
  async function runMemoryIndex(_agenrPath, dbPath) {
695
697
  try {
696
- return await withTimeout(loadMemoryIndex(dbPath), MEMORY_INDEX_TIMEOUT_MS);
697
- } catch {
698
- return null;
698
+ const result = await withTimeout(loadMemoryIndex(dbPath), MEMORY_INDEX_TIMEOUT_MS);
699
+ if (result === MEMORY_INDEX_TIMEOUT) {
700
+ return { status: "timeout" };
701
+ }
702
+ return result;
703
+ } catch (error) {
704
+ return {
705
+ status: "error",
706
+ error: toErrorMessage(error)
707
+ };
699
708
  }
700
709
  }
701
710
  async function loadMemoryIndex(dbPath) {
@@ -703,14 +712,24 @@ async function loadMemoryIndex(dbPath) {
703
712
  index: true,
704
713
  dbPath
705
714
  });
706
- return result.mode === "index" ? parseMemoryIndex(result.indexPayload) : null;
715
+ if (result.mode !== "index") {
716
+ return { status: "invalid" };
717
+ }
718
+ const index = parseMemoryIndex(result.indexPayload);
719
+ if (!index) {
720
+ return { status: "invalid" };
721
+ }
722
+ return {
723
+ status: "ok",
724
+ index
725
+ };
707
726
  }
708
727
  async function withTimeout(promise, timeoutMs) {
709
728
  let timer;
710
729
  try {
711
730
  return await new Promise((resolve, reject) => {
712
731
  timer = setTimeout(() => {
713
- resolve(null);
732
+ resolve(MEMORY_INDEX_TIMEOUT);
714
733
  }, timeoutMs);
715
734
  promise.then(resolve).catch(reject);
716
735
  });
@@ -755,34 +774,45 @@ function buildSpawnArgs(agenrPath) {
755
774
  }
756
775
  return { cmd: agenrPath, args: [] };
757
776
  }
758
- async function runRecall(_agenrPath, budget, project, query, options, dbPath) {
777
+ function buildRecallServiceRequest(budget, project, query, options, dbPath) {
778
+ const isBrowse = options?.context === "browse";
779
+ const trimmedQuery = query?.trim() ?? "";
780
+ const truncatedQuery = trimmedQuery.length > RECALL_QUERY_MAX_CHARS ? trimmedQuery.slice(0, RECALL_QUERY_MAX_CHARS) : trimmedQuery;
781
+ return {
782
+ queryInput: isBrowse ? void 0 : truncatedQuery || void 0,
783
+ context: isBrowse ? "browse" : "session-start",
784
+ browse: isBrowse,
785
+ since: isBrowse ? options?.since ?? "1d" : void 0,
786
+ limit: options?.limit,
787
+ minImportance: options?.minImportance,
788
+ expiry: options?.expiry,
789
+ universalOnly: options?.nullProjectOnly === true || options?.universalOnly === true,
790
+ project: options?.nullProjectOnly || options?.universalOnly || !project ? void 0 : [project],
791
+ projectStrict: Boolean(project && !options?.nullProjectOnly && !options?.universalOnly),
792
+ budget: isBrowse ? void 0 : budget,
793
+ dbPath,
794
+ metadataMode: "non-browse"
795
+ };
796
+ }
797
+ function shapeRecallResult(result) {
798
+ return {
799
+ query: result.payload.query,
800
+ results: result.payload.results.map((item) => ({
801
+ entry: item.entry,
802
+ score: item.score,
803
+ category: item.category
804
+ }))
805
+ };
806
+ }
807
+ async function runRecallStrict(_agenrPath, budget, project, query, options, dbPath) {
808
+ const result = await runRecallService(
809
+ buildRecallServiceRequest(budget, project, query, options, dbPath)
810
+ );
811
+ return shapeRecallResult(result);
812
+ }
813
+ async function runRecall(agenrPath, budget, project, query, options, dbPath) {
759
814
  try {
760
- const isBrowse = options?.context === "browse";
761
- const trimmedQuery = query?.trim() ?? "";
762
- const truncatedQuery = trimmedQuery.length > RECALL_QUERY_MAX_CHARS ? trimmedQuery.slice(0, RECALL_QUERY_MAX_CHARS) : trimmedQuery;
763
- const result = await runRecallService({
764
- queryInput: isBrowse ? void 0 : truncatedQuery || void 0,
765
- context: isBrowse ? "browse" : "session-start",
766
- browse: isBrowse,
767
- since: isBrowse ? options?.since ?? "1d" : void 0,
768
- limit: options?.limit,
769
- minImportance: options?.minImportance,
770
- expiry: options?.expiry,
771
- universalOnly: options?.nullProjectOnly === true || options?.universalOnly === true,
772
- project: options?.nullProjectOnly || options?.universalOnly || !project ? void 0 : [project],
773
- projectStrict: Boolean(project && !options?.nullProjectOnly && !options?.universalOnly),
774
- budget: isBrowse ? void 0 : budget,
775
- dbPath,
776
- metadataMode: "non-browse"
777
- });
778
- return {
779
- query: result.payload.query,
780
- results: result.payload.results.map((item) => ({
781
- entry: item.entry,
782
- score: item.score,
783
- category: item.category
784
- }))
785
- };
815
+ return await runRecallStrict(agenrPath, budget, project, query, options, dbPath);
786
816
  } catch {
787
817
  return null;
788
818
  }
@@ -2806,15 +2836,17 @@ function resolveSessionFamilyPolicy(currentFamily, predecessorFamily, options) {
2806
2836
  const normalizedPredecessorFamily = normalizePolicyFamily(predecessorFamily);
2807
2837
  const sameKnownFamily = currentFamily !== "unknown" && normalizedPredecessorFamily !== "unknown" && normalizedPredecessorFamily === currentFamily;
2808
2838
  const webchatLaneIdentityAvailable = sameKnownFamily && currentFamily === "webchat" && normalizedPredecessorFamily === "webchat" && (hasStableLaneIdentity(currentFamily, options?.currentLaneId) || hasStableLaneIdentity(normalizedPredecessorFamily, options?.predecessorLaneId));
2809
- const laneContinuityRequired = sameKnownFamily && (requiresStrictLaneScopedContinuity(currentFamily) || requiresStrictLaneScopedContinuity(normalizedPredecessorFamily) || webchatLaneIdentityAvailable);
2810
- const laneContinuityConfirmed = laneContinuityRequired ? options?.laneContinuityConfirmed === true : sameKnownFamily;
2811
- const predecessorContinuityEligible = sameKnownFamily && laneContinuityConfirmed;
2839
+ const degradedLaneFallbackUsed = sameKnownFamily && options?.degradedSameFamilyFallbackAllowed === true;
2840
+ const laneContinuityRequired = !degradedLaneFallbackUsed && sameKnownFamily && (requiresStrictLaneScopedContinuity(currentFamily) || requiresStrictLaneScopedContinuity(normalizedPredecessorFamily) || webchatLaneIdentityAvailable);
2841
+ const laneContinuityConfirmed = degradedLaneFallbackUsed ? true : laneContinuityRequired ? options?.laneContinuityConfirmed === true : sameKnownFamily;
2842
+ const predecessorContinuityEligible = sameKnownFamily && (degradedLaneFallbackUsed || laneContinuityConfirmed);
2812
2843
  return {
2813
2844
  currentFamily,
2814
2845
  predecessorFamily: normalizedPredecessorFamily,
2815
2846
  laneContinuityRequired,
2816
2847
  laneContinuityConfirmed,
2817
2848
  predecessorContinuityEligible,
2849
+ degradedLaneFallbackUsed,
2818
2850
  startupHandoffBrowseEntriesAllowed: predecessorContinuityEligible,
2819
2851
  sessionProjectInheritanceAllowed: predecessorContinuityEligible,
2820
2852
  clearedSessionProjectInheritanceAllowed: predecessorContinuityEligible
@@ -3968,8 +4000,14 @@ function isFallbackLaneContinuityConfirmed(currentIdentity, predecessorFamily, p
3968
4000
  }
3969
4001
  return currentIdentity.laneId === predecessorLaneId;
3970
4002
  }
4003
+ function shouldAllowDegradedTuiFallback(currentIdentity, predecessorFamily) {
4004
+ return currentIdentity.family === "tui" && predecessorFamily === "tui" && !currentIdentity.laneId;
4005
+ }
3971
4006
  function describeContinuityDecisionReason(currentFamily, predecessorFamily, currentLaneId, predecessorLaneId, policy) {
3972
4007
  if (policy.predecessorContinuityEligible) {
4008
+ if (policy.degradedLaneFallbackUsed) {
4009
+ return "same-family continuity accepted (tui degraded fallback: current lane unknown)";
4010
+ }
3973
4011
  if (currentFamily === "webchat" && predecessorFamily === "webchat" && !policy.laneContinuityRequired) {
3974
4012
  return "same-family continuity accepted (webchat single-lane runtime)";
3975
4013
  }
@@ -4224,6 +4262,10 @@ async function resolveSessionContinuity(event, ctx, sessionsDir, logger, debug,
4224
4262
  currentIdentity,
4225
4263
  predecessorFamilySignal.family,
4226
4264
  predecessorLaneSignal.laneId
4265
+ ),
4266
+ degradedSameFamilyFallbackAllowed: shouldAllowDegradedTuiFallback(
4267
+ currentIdentity,
4268
+ predecessorFamilySignal.family
4227
4269
  )
4228
4270
  });
4229
4271
  const predecessorIdentity = {
@@ -4562,7 +4604,79 @@ function diversifySessionStartBrowseCandidates(browseResult, limit = SESSION_STA
4562
4604
 
4563
4605
  // src/openclaw-plugin/hooks/before-prompt-build-session-start-recall.ts
4564
4606
  var SESSION_START_BROWSE_SINCE = "30d";
4607
+ var SQLITE_BUSY_RETRY_DELAYS_MS = [100, 200, 400];
4565
4608
  var sessionStartLog3 = createLogger("session-start");
4609
+ function formatConfiguredCoreProjects(coreProjects) {
4610
+ return `[${coreProjects.join(",")}]`;
4611
+ }
4612
+ function describeMemoryIndexResult(result) {
4613
+ if (!result) {
4614
+ return "memory index unavailable: unknown failure";
4615
+ }
4616
+ switch (result.status) {
4617
+ case "ok":
4618
+ return `memory index loaded: ${result.index.projects.length} projects`;
4619
+ case "timeout":
4620
+ return "memory index unavailable: timeout";
4621
+ case "error":
4622
+ return `memory index unavailable: ${result.error}`;
4623
+ case "invalid":
4624
+ return "memory index unavailable: invalid response";
4625
+ }
4626
+ }
4627
+ function isSqliteBusyMessage(message) {
4628
+ return message.toUpperCase().includes("SQLITE_BUSY");
4629
+ }
4630
+ async function sleepMs(ms) {
4631
+ await new Promise((resolve) => setTimeout(resolve, ms));
4632
+ }
4633
+ async function loadSessionStartBrowseWithRetry(params, project, options) {
4634
+ for (let attempt = 0; ; attempt += 1) {
4635
+ try {
4636
+ return await runRecallStrict(
4637
+ params.agenrPath,
4638
+ params.budget,
4639
+ project,
4640
+ void 0,
4641
+ options,
4642
+ params.config?.dbPath
4643
+ );
4644
+ } catch (error) {
4645
+ const message = toErrorMessage(error);
4646
+ const isBusy = isSqliteBusyMessage(message);
4647
+ if (!isBusy || attempt >= SQLITE_BUSY_RETRY_DELAYS_MS.length) {
4648
+ sessionStartLog3.warn(`browse unavailable: ${message}`);
4649
+ return null;
4650
+ }
4651
+ const delayMs = SQLITE_BUSY_RETRY_DELAYS_MS[attempt] ?? 400;
4652
+ sessionStartLog3.warn(
4653
+ `browse hit SQLITE_BUSY, retrying in ${delayMs}ms (attempt ${attempt + 1}/${SQLITE_BUSY_RETRY_DELAYS_MS.length + 1})`
4654
+ );
4655
+ await sleepMs(delayMs);
4656
+ }
4657
+ }
4658
+ }
4659
+ async function loadMemoryIndexWithRetry(params) {
4660
+ for (let attempt = 0; ; attempt += 1) {
4661
+ const result = await runMemoryIndex(
4662
+ params.agenrPath,
4663
+ params.config?.dbPath
4664
+ );
4665
+ const message = result.status === "error" ? result.error : "";
4666
+ const isBusy = result.status === "error" && isSqliteBusyMessage(message);
4667
+ if (!isBusy || attempt >= SQLITE_BUSY_RETRY_DELAYS_MS.length) {
4668
+ if (result.status !== "ok") {
4669
+ sessionStartLog3.warn(describeMemoryIndexResult(result));
4670
+ }
4671
+ return result;
4672
+ }
4673
+ const delayMs = SQLITE_BUSY_RETRY_DELAYS_MS[attempt] ?? 400;
4674
+ sessionStartLog3.warn(
4675
+ `memory index hit SQLITE_BUSY, retrying in ${delayMs}ms (attempt ${attempt + 1}/${SQLITE_BUSY_RETRY_DELAYS_MS.length + 1})`
4676
+ );
4677
+ await sleepMs(delayMs);
4678
+ }
4679
+ }
4566
4680
  async function fetchSessionStartRecallData(currentSessionProjectKey, params) {
4567
4681
  const coreProjects = params.config?.coreProjects ?? [];
4568
4682
  const sessionStartBrowseProject = await resolveSessionStartBrowseProject(
@@ -4581,18 +4695,8 @@ async function fetchSessionStartRecallData(currentSessionProjectKey, params) {
4581
4695
  }
4582
4696
  const [coreSettled, browseSettled, memoryIndexSettled] = await Promise.allSettled([
4583
4697
  fetchCoreEntries(params.agenrPath, coreProjects, params.config?.dbPath),
4584
- runRecall(
4585
- params.agenrPath,
4586
- params.budget,
4587
- sessionStartBrowseProject,
4588
- void 0,
4589
- browseRecallOptions,
4590
- params.config?.dbPath
4591
- ),
4592
- runMemoryIndex(
4593
- params.agenrPath,
4594
- params.config?.dbPath
4595
- )
4698
+ loadSessionStartBrowseWithRetry(params, sessionStartBrowseProject, browseRecallOptions),
4699
+ loadMemoryIndexWithRetry(params)
4596
4700
  ]);
4597
4701
  if (coreSettled.status === "rejected") {
4598
4702
  sessionStartLog3.error(`core recall failed: ${toErrorMessage(coreSettled.reason)}`);
@@ -4606,18 +4710,22 @@ async function fetchSessionStartRecallData(currentSessionProjectKey, params) {
4606
4710
  const coreResult = coreSettled.status === "fulfilled" ? coreSettled.value : null;
4607
4711
  const browseResult = browseSettled.status === "fulfilled" ? browseSettled.value : null;
4608
4712
  const memoryIndexResult = memoryIndexSettled.status === "fulfilled" ? memoryIndexSettled.value : null;
4609
- const memoryIndexMarkdown = memoryIndexResult ? formatMemoryIndex(memoryIndexResult) : void 0;
4713
+ const memoryIndexMarkdown = memoryIndexResult?.status === "ok" ? formatMemoryIndex(memoryIndexResult.index) : void 0;
4610
4714
  debugLog(
4611
4715
  params.debug,
4612
4716
  "session-start",
4613
- `core recall returned ${coreResult?.results.length ?? 0} entries (${coreProjects.length} active projects)`
4614
- );
4615
- debugLog(params.debug, "session-start", `browse returned ${browseResult?.results.length ?? 0} entries`);
4616
- debugLog(
4617
- params.debug,
4618
- "session-start",
4619
- `memory index returned ${memoryIndexResult?.projects.length ?? 0} projects`
4717
+ `core recall returned ${coreResult?.results.length ?? 0} entries (configured coreProjects=${formatConfiguredCoreProjects(coreProjects)})`
4620
4718
  );
4719
+ if (browseResult) {
4720
+ debugLog(params.debug, "session-start", `browse returned ${browseResult.results.length} entries`);
4721
+ }
4722
+ if (memoryIndexResult?.status === "ok") {
4723
+ debugLog(
4724
+ params.debug,
4725
+ "session-start",
4726
+ describeMemoryIndexResult(memoryIndexResult)
4727
+ );
4728
+ }
4621
4729
  return {
4622
4730
  coreResult,
4623
4731
  browseResult,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agenr",
3
- "version": "0.9.78",
3
+ "version": "0.9.80",
4
4
  "openclaw": {
5
5
  "extensions": [
6
6
  "dist/openclaw-plugin/index.js"