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 +19 -0
- package/dist/openclaw-plugin/index.js +164 -56
- package/package.json +1 -1
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
|
-
|
|
697
|
-
|
|
698
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2810
|
-
const
|
|
2811
|
-
const
|
|
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
|
-
|
|
4585
|
-
|
|
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 (
|
|
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,
|