agenr 0.9.74 → 0.9.75
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/{chunk-KTBSXLVU.js → chunk-AQN6BP74.js} +2 -2
- package/dist/{chunk-PAO647AF.js → chunk-HEE4JOTJ.js} +1 -1
- package/dist/{chunk-JH355OHS.js → chunk-PUBBITKQ.js} +2 -2
- package/dist/{chunk-Z7MN65K2.js → chunk-UGIJJOPV.js} +7 -0
- package/dist/cli-main.js +8 -8
- package/dist/{co-recall-MOA5R4D6.js → co-recall-VWYTWS7P.js} +1 -1
- package/dist/{maintain-CKA7EVOW.js → maintain-QRHVQ45A.js} +3 -3
- package/dist/openclaw-plugin/index.js +753 -65
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.9.75] - 2026-03-07
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- OpenClaw session-start memory injection now runs browse/core recall through a dedicated startup selector that favors continuity utility over raw browse ordering, suppresses overlap with `Recent session`, and applies hard section caps before rendering.
|
|
8
|
+
- Session-start startup memory now treats `fact`, `event`, `lesson`, `relationship`, and `reflection` intentionally instead of sending all residual non-core entries into one broad startup bucket.
|
|
9
|
+
- The session-start composer now reserves space for `Memory Index` before rendering `Recent memory`, so aggressive char trimming keeps recovery breadcrumbs whenever they still fit.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- OpenClaw session-start dedupe now records only the entry IDs that actually survive selector filtering and prompt rendering, so startup-trimmed entries remain eligible for later mid-session recall.
|
|
14
|
+
- Session-start selector freshness now treats missing or malformed `updated_at` values conservatively as stale instead of accidentally boosting them as fresh.
|
|
15
|
+
- Tightened session-start exact-state detection to require structured operational state such as active branch/worktree/cwd, numbered PR or issue references, concrete path or file state, or explicit config/env/version state.
|
|
16
|
+
- Added a narrow older-continuity escape hatch so importance-7 lessons, relationships, and exact-state or open-thread facts can survive beyond the freshness window when they still materially affect current work.
|
|
17
|
+
- OpenClaw session-start browse candidate assembly now prefetches a small larger pool, caps `reflection` entries before the final 10-slot cutoff, and backfills remaining slots by existing browse score so durable non-reflection entries are not starved upstream of the selector.
|
|
18
|
+
- Tightened OpenClaw session-start selector admission for historical decisions and preferences so high importance now boosts ranking only after current behavioral, exact-state, or real continuity evidence is present.
|
|
19
|
+
- Made session-start `openThread` detection more precise by treating weak narrative words like `next`, `continue`, and `fix` as continuity signals only when they are very recent, while preserving stronger unfinished-work cues.
|
|
20
|
+
- Removed a redundant post-classification cosmetic-preference filter from session-start selection and added regression coverage for stale high-importance decisions and weak versus strong continuity wording.
|
|
21
|
+
|
|
3
22
|
## [0.9.74] - 2026-03-07
|
|
4
23
|
|
|
5
24
|
### Added
|
|
@@ -3,12 +3,12 @@ import {
|
|
|
3
3
|
getDb,
|
|
4
4
|
initDb,
|
|
5
5
|
normalizeProject
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-HEE4JOTJ.js";
|
|
7
7
|
import {
|
|
8
8
|
toNumber,
|
|
9
9
|
toRowsAffected,
|
|
10
10
|
toStringValue
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-UGIJJOPV.js";
|
|
12
12
|
|
|
13
13
|
// src/openclaw-plugin/plugin-db.ts
|
|
14
14
|
var CREATE_SEEN_SESSIONS_TABLE_SQL = `
|
|
@@ -17,14 +17,14 @@ import {
|
|
|
17
17
|
runSimpleStream,
|
|
18
18
|
toErrorMessage,
|
|
19
19
|
walCheckpoint
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-HEE4JOTJ.js";
|
|
21
21
|
import {
|
|
22
22
|
MILLISECONDS_PER_DAY,
|
|
23
23
|
getCoRecallNeighbors,
|
|
24
24
|
parseDaysBetween,
|
|
25
25
|
toNumber,
|
|
26
26
|
toStringValue
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-UGIJJOPV.js";
|
|
28
28
|
|
|
29
29
|
// src/utils/parse.ts
|
|
30
30
|
function parsePositiveInt(value, fallback, label) {
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __export = (target, all) => {
|
|
3
|
+
for (var name in all)
|
|
4
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
|
+
};
|
|
6
|
+
|
|
1
7
|
// src/utils/entry-utils.ts
|
|
2
8
|
var MILLISECONDS_PER_DAY = 1e3 * 60 * 60 * 24;
|
|
3
9
|
function toNumber(value) {
|
|
@@ -321,6 +327,7 @@ async function getTopCoRecallEdges(db, limit = 20) {
|
|
|
321
327
|
}
|
|
322
328
|
|
|
323
329
|
export {
|
|
330
|
+
__export,
|
|
324
331
|
MILLISECONDS_PER_DAY,
|
|
325
332
|
toNumber,
|
|
326
333
|
toStringValue,
|
package/dist/cli-main.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
rehabilitateEntry,
|
|
10
10
|
resolveOpenClawWatcherSessionProjectState,
|
|
11
11
|
resolveReview
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-AQN6BP74.js";
|
|
13
13
|
import {
|
|
14
14
|
ConflictAlreadyResolvedError,
|
|
15
15
|
DEFAULT_AROUND_RADIUS_DAYS,
|
|
@@ -70,7 +70,7 @@ import {
|
|
|
70
70
|
toRecord,
|
|
71
71
|
updateRecallMetadata,
|
|
72
72
|
warnIfLocked
|
|
73
|
-
} from "./chunk-
|
|
73
|
+
} from "./chunk-PUBBITKQ.js";
|
|
74
74
|
import {
|
|
75
75
|
APP_VERSION,
|
|
76
76
|
DEFAULT_DB_PATH,
|
|
@@ -131,7 +131,7 @@ import {
|
|
|
131
131
|
toErrorMessage,
|
|
132
132
|
walCheckpoint,
|
|
133
133
|
writeConfig
|
|
134
|
-
} from "./chunk-
|
|
134
|
+
} from "./chunk-HEE4JOTJ.js";
|
|
135
135
|
import {
|
|
136
136
|
getCoRecallNeighbors,
|
|
137
137
|
getTopCoRecallEdges,
|
|
@@ -139,7 +139,7 @@ import {
|
|
|
139
139
|
toNumber,
|
|
140
140
|
toRowsAffected,
|
|
141
141
|
toStringValue
|
|
142
|
-
} from "./chunk-
|
|
142
|
+
} from "./chunk-UGIJJOPV.js";
|
|
143
143
|
|
|
144
144
|
// src/cli-main.ts
|
|
145
145
|
import * as clack18 from "@clack/prompts";
|
|
@@ -14795,7 +14795,7 @@ async function processTarget(params) {
|
|
|
14795
14795
|
try {
|
|
14796
14796
|
const existingIds = await queue.runExclusive(() => getSourceEntryIds(db, target.file));
|
|
14797
14797
|
if (existingIds.size >= 2) {
|
|
14798
|
-
const { strengthenCoRecallEdges, MAX_USED_ENTRIES } = await import("./co-recall-
|
|
14798
|
+
const { strengthenCoRecallEdges, MAX_USED_ENTRIES } = await import("./co-recall-VWYTWS7P.js");
|
|
14799
14799
|
const idArr = [...existingIds].slice(0, MAX_USED_ENTRIES);
|
|
14800
14800
|
const expectedEdges = idArr.length * (idArr.length - 1) / 2;
|
|
14801
14801
|
const edgeCount = await queue.runExclusive(async () => {
|
|
@@ -14966,7 +14966,7 @@ async function processTarget(params) {
|
|
|
14966
14966
|
try {
|
|
14967
14967
|
const fileEntryIds = await queue.runExclusive(() => getSourceEntryIds(db, target.file));
|
|
14968
14968
|
if (fileEntryIds.size >= 2) {
|
|
14969
|
-
const { strengthenCoRecallEdges } = await import("./co-recall-
|
|
14969
|
+
const { strengthenCoRecallEdges } = await import("./co-recall-VWYTWS7P.js");
|
|
14970
14970
|
const edgeTimestamp = resolvedDeps.nowFn().toISOString();
|
|
14971
14971
|
await queue.runExclusive(() => strengthenCoRecallEdges(db, [...fileEntryIds], edgeTimestamp));
|
|
14972
14972
|
}
|
|
@@ -18950,12 +18950,12 @@ function registerMaintainCommand(program) {
|
|
|
18950
18950
|
"--only <tasks>",
|
|
18951
18951
|
"Comma-separated task names: quality,edge-decay,clusters,conflicts,consolidation,retirement,reflection"
|
|
18952
18952
|
).option("--prune-edges", "When edge decay runs, delete edges below the configured decay floor").option("--verbose", "Show detailed per-task output").action(async (opts) => {
|
|
18953
|
-
const { runMaintainCommand } = await import("./maintain-
|
|
18953
|
+
const { runMaintainCommand } = await import("./maintain-QRHVQ45A.js");
|
|
18954
18954
|
const result = await runMaintainCommand(opts);
|
|
18955
18955
|
process.exitCode = result.exitCode;
|
|
18956
18956
|
});
|
|
18957
18957
|
maintainCommand.command("history").description("Show past maintenance runs").option("--db <path>", "Database path override").option("--limit <n>", "Max runs to show (default: 10)", parseIntOption).option("--json", "Output as JSON").action(async (opts) => {
|
|
18958
|
-
const { runMaintainHistoryCommand } = await import("./maintain-
|
|
18958
|
+
const { runMaintainHistoryCommand } = await import("./maintain-QRHVQ45A.js");
|
|
18959
18959
|
const result = await runMaintainHistoryCommand(opts);
|
|
18960
18960
|
process.exitCode = result.exitCode;
|
|
18961
18961
|
});
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
resolveModelForLlmClient,
|
|
28
28
|
runConsolidationOrchestrator,
|
|
29
29
|
toRecord
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-PUBBITKQ.js";
|
|
31
31
|
import {
|
|
32
32
|
DEFAULT_DB_PATH,
|
|
33
33
|
DEFAULT_TASK_MODEL,
|
|
@@ -43,14 +43,14 @@ import {
|
|
|
43
43
|
resolveEmbeddingApiKey,
|
|
44
44
|
runSimpleStream,
|
|
45
45
|
walCheckpoint
|
|
46
|
-
} from "./chunk-
|
|
46
|
+
} from "./chunk-HEE4JOTJ.js";
|
|
47
47
|
import {
|
|
48
48
|
decayCoRecallEdges,
|
|
49
49
|
getLastRecallActivity,
|
|
50
50
|
parseDaysBetween,
|
|
51
51
|
toNumber,
|
|
52
52
|
toStringValue
|
|
53
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-UGIJJOPV.js";
|
|
54
54
|
|
|
55
55
|
// src/llm/maintain-llm.ts
|
|
56
56
|
var MaintainLlmError = class extends Error {
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
getSessionProjectState,
|
|
8
8
|
resolveProjectWorthySessionProject,
|
|
9
9
|
setSessionProject
|
|
10
|
-
} from "../chunk-
|
|
10
|
+
} from "../chunk-AQN6BP74.js";
|
|
11
11
|
import {
|
|
12
12
|
KNOWLEDGE_TYPES,
|
|
13
13
|
SCOPE_LEVELS,
|
|
@@ -20,13 +20,14 @@ import {
|
|
|
20
20
|
resolveProjectScopeFromSlug,
|
|
21
21
|
runSimpleStream,
|
|
22
22
|
toErrorMessage
|
|
23
|
-
} from "../chunk-
|
|
23
|
+
} from "../chunk-HEE4JOTJ.js";
|
|
24
24
|
import {
|
|
25
25
|
MILLISECONDS_PER_DAY,
|
|
26
|
+
__export,
|
|
26
27
|
strengthenCoRecallEdges,
|
|
27
28
|
toNumber,
|
|
28
29
|
toStringValue
|
|
29
|
-
} from "../chunk-
|
|
30
|
+
} from "../chunk-UGIJJOPV.js";
|
|
30
31
|
|
|
31
32
|
// src/openclaw-plugin/index.ts
|
|
32
33
|
import os3 from "os";
|
|
@@ -615,6 +616,19 @@ function formatMidSessionRecall(results, options = {}) {
|
|
|
615
616
|
}
|
|
616
617
|
|
|
617
618
|
// src/openclaw-plugin/recall.ts
|
|
619
|
+
var recall_exports = {};
|
|
620
|
+
__export(recall_exports, {
|
|
621
|
+
buildSpawnArgs: () => buildSpawnArgs2,
|
|
622
|
+
fetchCoreEntries: () => fetchCoreEntries,
|
|
623
|
+
formatCoreAsMarkdown: () => formatCoreAsMarkdown,
|
|
624
|
+
formatMemoryIndex: () => formatMemoryIndex,
|
|
625
|
+
formatRecallAsMarkdown: () => formatRecallAsMarkdown,
|
|
626
|
+
renderRecallAsMarkdown: () => renderRecallAsMarkdown,
|
|
627
|
+
resolveAgenrPath: () => resolveAgenrPath,
|
|
628
|
+
resolveBudget: () => resolveBudget,
|
|
629
|
+
runMemoryIndex: () => runMemoryIndex,
|
|
630
|
+
runRecall: () => runRecall
|
|
631
|
+
});
|
|
618
632
|
import { spawn as spawn2 } from "child_process";
|
|
619
633
|
import path from "path";
|
|
620
634
|
import { fileURLToPath } from "url";
|
|
@@ -891,10 +905,14 @@ function groupValidEntries(result, options = {}) {
|
|
|
891
905
|
if (options.excludeCore && isCoreRecallItem(item)) {
|
|
892
906
|
continue;
|
|
893
907
|
}
|
|
894
|
-
const groupedEntry =
|
|
895
|
-
|
|
908
|
+
const groupedEntry = {
|
|
909
|
+
item,
|
|
910
|
+
entry: normalizeGroupedEntry(entry, options.addCoreMetadata === true)
|
|
911
|
+
};
|
|
912
|
+
const explicitCategory = item.category;
|
|
913
|
+
if (explicitCategory === "active" || TODO_TYPES.has(entry.type)) {
|
|
896
914
|
grouped.todos.push(groupedEntry);
|
|
897
|
-
} else if (PREFERENCE_TYPES.has(entry.type)) {
|
|
915
|
+
} else if (explicitCategory === "preferences" || PREFERENCE_TYPES.has(entry.type)) {
|
|
898
916
|
grouped.preferences.push(groupedEntry);
|
|
899
917
|
} else {
|
|
900
918
|
grouped.facts.push(groupedEntry);
|
|
@@ -911,17 +929,21 @@ function collectCoreEntries(result) {
|
|
|
911
929
|
if (!isCoreRecallItem(item) || !isValidEntry(item.entry)) {
|
|
912
930
|
continue;
|
|
913
931
|
}
|
|
914
|
-
entries.push(
|
|
932
|
+
entries.push({
|
|
933
|
+
item,
|
|
934
|
+
entry: normalizeGroupedEntry(item.entry, true)
|
|
935
|
+
});
|
|
915
936
|
}
|
|
916
937
|
return sortCorePriority(entries);
|
|
917
938
|
}
|
|
918
939
|
function buildSectionMarkdown(header, entries, maxEntryChars, maxChars) {
|
|
919
940
|
if (entries.length === 0 || maxChars <= 0) {
|
|
920
|
-
return { markdown: "", entryCount: 0 };
|
|
941
|
+
return { markdown: "", entryCount: 0, renderedItems: [] };
|
|
921
942
|
}
|
|
922
943
|
const lines = [];
|
|
923
944
|
let totalChars = 0;
|
|
924
945
|
let entryCount = 0;
|
|
946
|
+
const renderedItems = [];
|
|
925
947
|
const tryPushLine = (line) => {
|
|
926
948
|
const nextLength = totalChars + (lines.length > 0 ? 1 : 0) + line.length;
|
|
927
949
|
if (nextLength > maxChars) {
|
|
@@ -932,20 +954,21 @@ function buildSectionMarkdown(header, entries, maxEntryChars, maxChars) {
|
|
|
932
954
|
return true;
|
|
933
955
|
};
|
|
934
956
|
if (!tryPushLine(header) || !tryPushLine("")) {
|
|
935
|
-
return { markdown: "", entryCount: 0 };
|
|
957
|
+
return { markdown: "", entryCount: 0, renderedItems: [] };
|
|
936
958
|
}
|
|
937
959
|
for (const entry of entries) {
|
|
938
|
-
const line = `- [${entry.subject}] ${truncateEntryContent(entry.content, maxEntryChars)}`;
|
|
960
|
+
const line = `- [${entry.entry.subject}] ${truncateEntryContent(entry.entry.content, maxEntryChars)}`;
|
|
939
961
|
if (!tryPushLine(line)) {
|
|
940
962
|
break;
|
|
941
963
|
}
|
|
942
964
|
entryCount += 1;
|
|
965
|
+
renderedItems.push(entry.item);
|
|
943
966
|
}
|
|
944
967
|
if (entryCount === 0) {
|
|
945
|
-
return { markdown: "", entryCount: 0 };
|
|
968
|
+
return { markdown: "", entryCount: 0, renderedItems: [] };
|
|
946
969
|
}
|
|
947
970
|
tryPushLine("");
|
|
948
|
-
return { markdown: lines.join("\n").trimEnd(), entryCount };
|
|
971
|
+
return { markdown: lines.join("\n").trimEnd(), entryCount, renderedItems };
|
|
949
972
|
}
|
|
950
973
|
function createBlockAccumulator(maxChars) {
|
|
951
974
|
const blocks = [];
|
|
@@ -970,6 +993,9 @@ function createBlockAccumulator(maxChars) {
|
|
|
970
993
|
};
|
|
971
994
|
}
|
|
972
995
|
function formatRecallAsMarkdown(result, options = {}) {
|
|
996
|
+
return renderRecallAsMarkdown(result, options).markdown;
|
|
997
|
+
}
|
|
998
|
+
function renderRecallAsMarkdown(result, options = {}) {
|
|
973
999
|
const maxEntryChars = resolveFormatLimit(
|
|
974
1000
|
options.maxEntryChars,
|
|
975
1001
|
DEFAULT_FORMAT_MAX_ENTRY_CHARS2
|
|
@@ -978,36 +1004,46 @@ function formatRecallAsMarkdown(result, options = {}) {
|
|
|
978
1004
|
const coreMaxChars = resolveFormatLimit(options.coreMaxChars, maxChars);
|
|
979
1005
|
return formatRecallWithLimits(result, maxEntryChars, maxChars, {
|
|
980
1006
|
coreMaxChars,
|
|
981
|
-
includeRootHeader: options.includeRootHeader !== false
|
|
1007
|
+
includeRootHeader: options.includeRootHeader !== false,
|
|
1008
|
+
recentSectionHeader: options.recentSectionHeader ?? "### Facts and Events"
|
|
982
1009
|
});
|
|
983
1010
|
}
|
|
1011
|
+
function formatCoreAsMarkdown(result, options = {}) {
|
|
1012
|
+
const maxEntryChars = resolveFormatLimit(
|
|
1013
|
+
options.maxEntryChars,
|
|
1014
|
+
DEFAULT_FORMAT_MAX_ENTRY_CHARS2
|
|
1015
|
+
);
|
|
1016
|
+
const maxChars = resolveFormatLimit(options.maxChars, DEFAULT_FORMAT_MAX_CHARS2);
|
|
1017
|
+
return formatCoreWithLimits(result, maxEntryChars, maxChars);
|
|
1018
|
+
}
|
|
984
1019
|
function sortCorePriority(entries) {
|
|
985
1020
|
return [...entries].sort((a, b) => {
|
|
986
|
-
const aUniversal = a._isUniversal ?? false;
|
|
987
|
-
const bUniversal = b._isUniversal ?? false;
|
|
1021
|
+
const aUniversal = a.entry._isUniversal ?? false;
|
|
1022
|
+
const bUniversal = b.entry._isUniversal ?? false;
|
|
988
1023
|
if (aUniversal !== bUniversal) {
|
|
989
1024
|
return aUniversal ? -1 : 1;
|
|
990
1025
|
}
|
|
991
|
-
const aImportance = a._importance ?? 0;
|
|
992
|
-
const bImportance = b._importance ?? 0;
|
|
1026
|
+
const aImportance = a.entry._importance ?? 0;
|
|
1027
|
+
const bImportance = b.entry._importance ?? 0;
|
|
993
1028
|
if (aImportance !== bImportance) {
|
|
994
1029
|
return bImportance - aImportance;
|
|
995
1030
|
}
|
|
996
|
-
return a.subject.localeCompare(b.subject);
|
|
1031
|
+
return a.entry.subject.localeCompare(b.entry.subject);
|
|
997
1032
|
});
|
|
998
1033
|
}
|
|
999
1034
|
function formatRecallWithLimits(result, maxEntryChars, maxChars, options) {
|
|
1000
1035
|
if (!result.results || result.results.length === 0) {
|
|
1001
|
-
return "";
|
|
1036
|
+
return { markdown: "", renderedItems: [] };
|
|
1002
1037
|
}
|
|
1003
1038
|
const coreEntries = collectCoreEntries(result);
|
|
1004
1039
|
const { todos, preferences, facts } = groupValidEntries(result, { excludeCore: true });
|
|
1005
1040
|
if (coreEntries.length === 0 && todos.length === 0 && preferences.length === 0 && facts.length === 0) {
|
|
1006
|
-
return "";
|
|
1041
|
+
return { markdown: "", renderedItems: [] };
|
|
1007
1042
|
}
|
|
1008
1043
|
const blocks = createBlockAccumulator(maxChars);
|
|
1044
|
+
const renderedItems = [];
|
|
1009
1045
|
if (options.includeRootHeader && !blocks.tryPushBlock("## agenr Memory Context", 0)) {
|
|
1010
|
-
return blocks.finalize();
|
|
1046
|
+
return { markdown: blocks.finalize(), renderedItems };
|
|
1011
1047
|
}
|
|
1012
1048
|
if (coreEntries.length > 0) {
|
|
1013
1049
|
const coreBudget = Math.min(blocks.remainingChars(), options.coreMaxChars);
|
|
@@ -1018,13 +1054,47 @@ function formatRecallWithLimits(result, maxEntryChars, maxChars, options) {
|
|
|
1018
1054
|
coreBudget
|
|
1019
1055
|
);
|
|
1020
1056
|
if (!blocks.tryPushBlock(coreSection.markdown, coreSection.entryCount)) {
|
|
1021
|
-
return blocks.finalize();
|
|
1057
|
+
return { markdown: blocks.finalize(), renderedItems };
|
|
1022
1058
|
}
|
|
1059
|
+
renderedItems.push(...coreSection.renderedItems);
|
|
1023
1060
|
}
|
|
1024
1061
|
for (const [header, entries] of [
|
|
1025
1062
|
["### Active Todos", todos],
|
|
1026
1063
|
["### Preferences and Decisions", preferences],
|
|
1027
|
-
[
|
|
1064
|
+
[options.recentSectionHeader, facts]
|
|
1065
|
+
]) {
|
|
1066
|
+
const section = buildSectionMarkdown(
|
|
1067
|
+
header,
|
|
1068
|
+
entries,
|
|
1069
|
+
maxEntryChars,
|
|
1070
|
+
blocks.remainingChars()
|
|
1071
|
+
);
|
|
1072
|
+
if (!blocks.tryPushBlock(section.markdown, section.entryCount)) {
|
|
1073
|
+
return { markdown: blocks.finalize(), renderedItems };
|
|
1074
|
+
}
|
|
1075
|
+
renderedItems.push(...section.renderedItems);
|
|
1076
|
+
}
|
|
1077
|
+
return { markdown: blocks.finalize(), renderedItems };
|
|
1078
|
+
}
|
|
1079
|
+
function formatCoreWithLimits(result, maxEntryChars, maxChars) {
|
|
1080
|
+
if (!result.results || result.results.length === 0) {
|
|
1081
|
+
return "";
|
|
1082
|
+
}
|
|
1083
|
+
let { todos, preferences, facts } = groupValidEntries(result, { addCoreMetadata: true });
|
|
1084
|
+
todos = sortCorePriority(todos);
|
|
1085
|
+
preferences = sortCorePriority(preferences);
|
|
1086
|
+
facts = sortCorePriority(facts);
|
|
1087
|
+
if (todos.length === 0 && preferences.length === 0 && facts.length === 0) {
|
|
1088
|
+
return "";
|
|
1089
|
+
}
|
|
1090
|
+
const blocks = createBlockAccumulator(maxChars);
|
|
1091
|
+
if (!blocks.tryPushBlock("### Core Context", 0)) {
|
|
1092
|
+
return blocks.finalize();
|
|
1093
|
+
}
|
|
1094
|
+
for (const [header, entries] of [
|
|
1095
|
+
["#### Active Todos", todos],
|
|
1096
|
+
["#### Preferences and Decisions", preferences],
|
|
1097
|
+
["#### Facts and Events", facts]
|
|
1028
1098
|
]) {
|
|
1029
1099
|
const section = buildSectionMarkdown(
|
|
1030
1100
|
header,
|
|
@@ -2641,6 +2711,576 @@ async function runHandoffForSession(opts) {
|
|
|
2641
2711
|
import os from "os";
|
|
2642
2712
|
import path4 from "path";
|
|
2643
2713
|
|
|
2714
|
+
// src/openclaw-plugin/session-start-selector.ts
|
|
2715
|
+
var DEFAULT_SESSION_START_SECTION_CAPS = {
|
|
2716
|
+
core: 6,
|
|
2717
|
+
active: 4,
|
|
2718
|
+
preferences: 4,
|
|
2719
|
+
recent: 2
|
|
2720
|
+
};
|
|
2721
|
+
var CATEGORY_PRIORITY = {
|
|
2722
|
+
core: 0,
|
|
2723
|
+
active: 1,
|
|
2724
|
+
preferences: 2,
|
|
2725
|
+
recent: 3
|
|
2726
|
+
};
|
|
2727
|
+
var BEHAVIOR_KEYWORDS = [
|
|
2728
|
+
"always",
|
|
2729
|
+
"avoid",
|
|
2730
|
+
"constraint",
|
|
2731
|
+
"default",
|
|
2732
|
+
"do not",
|
|
2733
|
+
"don't",
|
|
2734
|
+
"format",
|
|
2735
|
+
"must",
|
|
2736
|
+
"must not",
|
|
2737
|
+
"never",
|
|
2738
|
+
"policy",
|
|
2739
|
+
"rule",
|
|
2740
|
+
"style",
|
|
2741
|
+
"workflow"
|
|
2742
|
+
];
|
|
2743
|
+
var STRONG_OPEN_THREAD_KEYWORDS = [
|
|
2744
|
+
"blocker",
|
|
2745
|
+
"blocked",
|
|
2746
|
+
"failing",
|
|
2747
|
+
"follow up",
|
|
2748
|
+
"follow-up",
|
|
2749
|
+
"in progress",
|
|
2750
|
+
"open thread",
|
|
2751
|
+
"pending",
|
|
2752
|
+
"stuck",
|
|
2753
|
+
"todo",
|
|
2754
|
+
"unfinished",
|
|
2755
|
+
"waiting",
|
|
2756
|
+
"wip"
|
|
2757
|
+
];
|
|
2758
|
+
var WEAK_OPEN_THREAD_KEYWORDS = [
|
|
2759
|
+
"continue",
|
|
2760
|
+
"fix",
|
|
2761
|
+
"next",
|
|
2762
|
+
"remaining",
|
|
2763
|
+
"resume"
|
|
2764
|
+
];
|
|
2765
|
+
var OPERATIONAL_KEYWORDS = [
|
|
2766
|
+
"build",
|
|
2767
|
+
"check",
|
|
2768
|
+
"command",
|
|
2769
|
+
"debug",
|
|
2770
|
+
"deploy",
|
|
2771
|
+
"install",
|
|
2772
|
+
"rerun",
|
|
2773
|
+
"run",
|
|
2774
|
+
"ship",
|
|
2775
|
+
"test",
|
|
2776
|
+
"verify",
|
|
2777
|
+
"write"
|
|
2778
|
+
];
|
|
2779
|
+
var RELATIONSHIP_KEYWORDS = [
|
|
2780
|
+
"blocked by",
|
|
2781
|
+
"contact",
|
|
2782
|
+
"depends on",
|
|
2783
|
+
"maintainer",
|
|
2784
|
+
"owner",
|
|
2785
|
+
"paired with",
|
|
2786
|
+
"reviewer"
|
|
2787
|
+
];
|
|
2788
|
+
var ARCHIVAL_KEYWORDS = [
|
|
2789
|
+
"closed",
|
|
2790
|
+
"completed",
|
|
2791
|
+
"deprecated",
|
|
2792
|
+
"done",
|
|
2793
|
+
"fixed",
|
|
2794
|
+
"historical",
|
|
2795
|
+
"launched",
|
|
2796
|
+
"merged",
|
|
2797
|
+
"released",
|
|
2798
|
+
"resolved",
|
|
2799
|
+
"retired",
|
|
2800
|
+
"shipped",
|
|
2801
|
+
"used to"
|
|
2802
|
+
];
|
|
2803
|
+
var COSMETIC_PREFERENCE_KEYWORDS = [
|
|
2804
|
+
"dark mode",
|
|
2805
|
+
"font",
|
|
2806
|
+
"screenshot",
|
|
2807
|
+
"theme",
|
|
2808
|
+
"ui"
|
|
2809
|
+
];
|
|
2810
|
+
var EXACT_STATE_PATH_HINT_PATTERN = /\b(?:cwd|file|path|repo path|worktree)\b/i;
|
|
2811
|
+
var EXACT_STATE_PATH_VALUE_PATTERN = /(?:^|[\s(])(?:~\/|\/[^\s)]+|[A-Za-z0-9._-]+\/[A-Za-z0-9._/-]+|[A-Za-z0-9._-]+\.[A-Za-z0-9]{1,8})(?=$|[\s),])/;
|
|
2812
|
+
var EXACT_STATE_ISSUE_PATTERN = /\b(?:pr|issue)\s*#?\d+\b/i;
|
|
2813
|
+
var EXACT_STATE_COMMIT_PATTERN = /\bcommit\b/i;
|
|
2814
|
+
var EXACT_STATE_COMMIT_HASH_PATTERN = /\b[0-9a-f]{7,40}\b/i;
|
|
2815
|
+
var EXACT_STATE_TAG_PATTERN = /\btag\b/i;
|
|
2816
|
+
var EXACT_STATE_ENV_PATTERN = /\b(?:env|environment)\b/i;
|
|
2817
|
+
var EXACT_STATE_CONFIG_PATTERN = /\bconfig\b/i;
|
|
2818
|
+
var EXACT_STATE_VERSION_PATTERN = /\bversion\b/i;
|
|
2819
|
+
var EXACT_STATE_STATEFUL_PATTERN = /\b(?:active|current|disabled|enabled|pinned|points to|running|set to|using)\b/i;
|
|
2820
|
+
var EXACT_STATE_ENV_ASSIGNMENT_PATTERN = /\b[A-Z][A-Z0-9_]{1,}=[^\s]+\b/;
|
|
2821
|
+
var EXACT_STATE_SEMVER_PATTERN = /\bv?\d+\.\d+(?:\.\d+)?\b/i;
|
|
2822
|
+
var IMPORTANCE_SEVEN_CONTINUITY_MAX_AGE_DAYS = 120;
|
|
2823
|
+
function normalizeText(value) {
|
|
2824
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, " ").replace(/\s+/g, " ").trim();
|
|
2825
|
+
}
|
|
2826
|
+
function tokenize(value) {
|
|
2827
|
+
return normalizeText(value).split(" ").map((token) => token.trim()).filter((token) => token.length >= 4);
|
|
2828
|
+
}
|
|
2829
|
+
function toTimestamp(value) {
|
|
2830
|
+
if (typeof value !== "string") {
|
|
2831
|
+
return 0;
|
|
2832
|
+
}
|
|
2833
|
+
const timestamp = Date.parse(value);
|
|
2834
|
+
return Number.isFinite(timestamp) ? timestamp : 0;
|
|
2835
|
+
}
|
|
2836
|
+
function containsKeyword(text, keywords) {
|
|
2837
|
+
return keywords.some((keyword) => text.includes(keyword));
|
|
2838
|
+
}
|
|
2839
|
+
function hasOpenThreadSignal(text, veryRecent) {
|
|
2840
|
+
if (containsKeyword(text, STRONG_OPEN_THREAD_KEYWORDS)) {
|
|
2841
|
+
return true;
|
|
2842
|
+
}
|
|
2843
|
+
return veryRecent && containsKeyword(text, WEAK_OPEN_THREAD_KEYWORDS);
|
|
2844
|
+
}
|
|
2845
|
+
function overlapRatio(candidateText, referenceTokens) {
|
|
2846
|
+
if (referenceTokens.size === 0) {
|
|
2847
|
+
return 0;
|
|
2848
|
+
}
|
|
2849
|
+
const tokens = new Set(tokenize(candidateText));
|
|
2850
|
+
if (tokens.size === 0) {
|
|
2851
|
+
return 0;
|
|
2852
|
+
}
|
|
2853
|
+
let overlap = 0;
|
|
2854
|
+
for (const token of tokens) {
|
|
2855
|
+
if (referenceTokens.has(token)) {
|
|
2856
|
+
overlap += 1;
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
2859
|
+
return overlap / tokens.size;
|
|
2860
|
+
}
|
|
2861
|
+
function subjectKey(item) {
|
|
2862
|
+
const subject = typeof item.entry?.subject === "string" ? normalizeText(item.entry.subject) : "";
|
|
2863
|
+
return subject ? `subject:${subject}` : null;
|
|
2864
|
+
}
|
|
2865
|
+
function contentKey(item) {
|
|
2866
|
+
const content = typeof item.entry?.content === "string" ? normalizeText(item.entry.content) : "";
|
|
2867
|
+
return content ? `content:${content.slice(0, 200)}` : null;
|
|
2868
|
+
}
|
|
2869
|
+
function exactKey(item) {
|
|
2870
|
+
const id = typeof item.entry?.id === "string" ? item.entry.id.trim() : "";
|
|
2871
|
+
if (id) {
|
|
2872
|
+
return `id:${id}`;
|
|
2873
|
+
}
|
|
2874
|
+
const type = typeof item.entry?.type === "string" ? item.entry.type.trim() : "";
|
|
2875
|
+
const subject = typeof item.entry?.subject === "string" ? normalizeText(item.entry.subject) : "";
|
|
2876
|
+
const content = typeof item.entry?.content === "string" ? normalizeText(item.entry.content) : "";
|
|
2877
|
+
if (!type || !subject || !content) {
|
|
2878
|
+
return null;
|
|
2879
|
+
}
|
|
2880
|
+
return `entry:${type}\0${subject}\0${content}`;
|
|
2881
|
+
}
|
|
2882
|
+
function buildOverlapKeys(item) {
|
|
2883
|
+
return [exactKey(item), subjectKey(item), contentKey(item)].filter((value) => Boolean(value));
|
|
2884
|
+
}
|
|
2885
|
+
function getImportance(item) {
|
|
2886
|
+
const rawImportance = item.entry?.importance;
|
|
2887
|
+
return typeof rawImportance === "number" && Number.isFinite(rawImportance) ? rawImportance : 0;
|
|
2888
|
+
}
|
|
2889
|
+
function getAgeDays(item, now) {
|
|
2890
|
+
const updatedAt = toTimestamp(item.entry?.updated_at);
|
|
2891
|
+
if (updatedAt <= 0 || now <= 0) {
|
|
2892
|
+
return Number.POSITIVE_INFINITY;
|
|
2893
|
+
}
|
|
2894
|
+
return Math.max(0, (now - updatedAt) / (24 * 60 * 60 * 1e3));
|
|
2895
|
+
}
|
|
2896
|
+
function hasExactStateSignal(subject, content) {
|
|
2897
|
+
const text = normalizeText(`${subject} ${content}`);
|
|
2898
|
+
const rawText = `${subject} ${content}`;
|
|
2899
|
+
if (containsKeyword(text, ["branch", "cwd", "worktree"])) {
|
|
2900
|
+
return true;
|
|
2901
|
+
}
|
|
2902
|
+
if (EXACT_STATE_ISSUE_PATTERN.test(rawText)) {
|
|
2903
|
+
return true;
|
|
2904
|
+
}
|
|
2905
|
+
if (EXACT_STATE_COMMIT_PATTERN.test(text) && EXACT_STATE_COMMIT_HASH_PATTERN.test(rawText)) {
|
|
2906
|
+
return true;
|
|
2907
|
+
}
|
|
2908
|
+
if (EXACT_STATE_TAG_PATTERN.test(text) && EXACT_STATE_SEMVER_PATTERN.test(rawText)) {
|
|
2909
|
+
return true;
|
|
2910
|
+
}
|
|
2911
|
+
if (EXACT_STATE_PATH_HINT_PATTERN.test(text) && EXACT_STATE_PATH_VALUE_PATTERN.test(rawText)) {
|
|
2912
|
+
return true;
|
|
2913
|
+
}
|
|
2914
|
+
if (EXACT_STATE_ENV_PATTERN.test(text)) {
|
|
2915
|
+
return EXACT_STATE_ENV_ASSIGNMENT_PATTERN.test(rawText) || EXACT_STATE_STATEFUL_PATTERN.test(text);
|
|
2916
|
+
}
|
|
2917
|
+
if (EXACT_STATE_CONFIG_PATTERN.test(text) && EXACT_STATE_STATEFUL_PATTERN.test(text)) {
|
|
2918
|
+
return true;
|
|
2919
|
+
}
|
|
2920
|
+
if (EXACT_STATE_VERSION_PATTERN.test(text) && EXACT_STATE_SEMVER_PATTERN.test(rawText) && EXACT_STATE_STATEFUL_PATTERN.test(text)) {
|
|
2921
|
+
return true;
|
|
2922
|
+
}
|
|
2923
|
+
return false;
|
|
2924
|
+
}
|
|
2925
|
+
function buildSignals(item, now) {
|
|
2926
|
+
const subject = typeof item.entry?.subject === "string" ? item.entry.subject : "";
|
|
2927
|
+
const content = typeof item.entry?.content === "string" ? item.entry.content : "";
|
|
2928
|
+
const text = normalizeText(`${subject} ${content}`);
|
|
2929
|
+
const ageDays = getAgeDays(item, now);
|
|
2930
|
+
const exactState = hasExactStateSignal(subject, content);
|
|
2931
|
+
const veryRecent = ageDays <= 3;
|
|
2932
|
+
const openThread = hasOpenThreadSignal(text, veryRecent);
|
|
2933
|
+
const behaviorImpact = openThread || containsKeyword(text, BEHAVIOR_KEYWORDS);
|
|
2934
|
+
const operational = behaviorImpact || exactState || containsKeyword(text, OPERATIONAL_KEYWORDS);
|
|
2935
|
+
const strongRelationship = containsKeyword(text, RELATIONSHIP_KEYWORDS);
|
|
2936
|
+
return {
|
|
2937
|
+
archival: containsKeyword(text, ARCHIVAL_KEYWORDS),
|
|
2938
|
+
behaviorImpact,
|
|
2939
|
+
exactState,
|
|
2940
|
+
fresh: ageDays <= 14,
|
|
2941
|
+
operational,
|
|
2942
|
+
openThread,
|
|
2943
|
+
recent: ageDays <= 30,
|
|
2944
|
+
strongRelationship,
|
|
2945
|
+
veryRecent
|
|
2946
|
+
};
|
|
2947
|
+
}
|
|
2948
|
+
function hasImportanceSevenContinuityEscape(item, signals, now) {
|
|
2949
|
+
const importance = getImportance(item);
|
|
2950
|
+
if (importance < 7 || importance >= 8 || signals.archival) {
|
|
2951
|
+
return false;
|
|
2952
|
+
}
|
|
2953
|
+
const ageDays = getAgeDays(item, now);
|
|
2954
|
+
if (!Number.isFinite(ageDays) || ageDays <= 14 || ageDays > IMPORTANCE_SEVEN_CONTINUITY_MAX_AGE_DAYS) {
|
|
2955
|
+
return false;
|
|
2956
|
+
}
|
|
2957
|
+
switch (item.entry?.type) {
|
|
2958
|
+
case "lesson":
|
|
2959
|
+
return signals.operational;
|
|
2960
|
+
case "relationship":
|
|
2961
|
+
return signals.strongRelationship;
|
|
2962
|
+
case "fact":
|
|
2963
|
+
case "event":
|
|
2964
|
+
return signals.exactState || signals.behaviorImpact && signals.openThread;
|
|
2965
|
+
default:
|
|
2966
|
+
return false;
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
function classifyCandidate(item, signals, now) {
|
|
2970
|
+
const expiry = item.category === "core" || item.entry?.expiry === "core";
|
|
2971
|
+
if (expiry) {
|
|
2972
|
+
return "core";
|
|
2973
|
+
}
|
|
2974
|
+
const type = item.entry?.type;
|
|
2975
|
+
const importance = getImportance(item);
|
|
2976
|
+
const ageDays = getAgeDays(item, now);
|
|
2977
|
+
const recentEnough = ageDays <= 14;
|
|
2978
|
+
if (type === "todo") {
|
|
2979
|
+
return "active";
|
|
2980
|
+
}
|
|
2981
|
+
if (type === "decision" || type === "preference") {
|
|
2982
|
+
const text = normalizeText(`${item.entry?.subject ?? ""} ${item.entry?.content ?? ""}`);
|
|
2983
|
+
const hasCurrentEffect = signals.behaviorImpact || signals.exactState || signals.openThread;
|
|
2984
|
+
if (type === "preference" && importance < 8 && !signals.exactState && !signals.openThread && containsKeyword(text, COSMETIC_PREFERENCE_KEYWORDS)) {
|
|
2985
|
+
return null;
|
|
2986
|
+
}
|
|
2987
|
+
if (signals.archival && !hasCurrentEffect) {
|
|
2988
|
+
return null;
|
|
2989
|
+
}
|
|
2990
|
+
if (hasCurrentEffect) {
|
|
2991
|
+
return "preferences";
|
|
2992
|
+
}
|
|
2993
|
+
return null;
|
|
2994
|
+
}
|
|
2995
|
+
if (type === "reflection") {
|
|
2996
|
+
if (signals.behaviorImpact && signals.openThread && signals.veryRecent && importance >= 9) {
|
|
2997
|
+
return "recent";
|
|
2998
|
+
}
|
|
2999
|
+
return null;
|
|
3000
|
+
}
|
|
3001
|
+
if (type === "relationship") {
|
|
3002
|
+
if (!signals.strongRelationship && !signals.behaviorImpact && !signals.exactState) {
|
|
3003
|
+
return null;
|
|
3004
|
+
}
|
|
3005
|
+
if (recentEnough || importance >= 8 || hasImportanceSevenContinuityEscape(item, signals, now)) {
|
|
3006
|
+
return "recent";
|
|
3007
|
+
}
|
|
3008
|
+
return null;
|
|
3009
|
+
}
|
|
3010
|
+
if (type === "lesson") {
|
|
3011
|
+
if (!signals.operational) {
|
|
3012
|
+
return null;
|
|
3013
|
+
}
|
|
3014
|
+
if (recentEnough || importance >= 8 || hasImportanceSevenContinuityEscape(item, signals, now)) {
|
|
3015
|
+
return "recent";
|
|
3016
|
+
}
|
|
3017
|
+
return null;
|
|
3018
|
+
}
|
|
3019
|
+
if (type === "fact" || type === "event") {
|
|
3020
|
+
if (!signals.exactState && !signals.openThread && !signals.behaviorImpact && item.score < 0.8) {
|
|
3021
|
+
return null;
|
|
3022
|
+
}
|
|
3023
|
+
if (signals.archival && !signals.openThread && !signals.exactState) {
|
|
3024
|
+
return null;
|
|
3025
|
+
}
|
|
3026
|
+
if (recentEnough || importance >= 8 || hasImportanceSevenContinuityEscape(item, signals, now)) {
|
|
3027
|
+
return "recent";
|
|
3028
|
+
}
|
|
3029
|
+
return null;
|
|
3030
|
+
}
|
|
3031
|
+
return null;
|
|
3032
|
+
}
|
|
3033
|
+
function computeUtility(item, category, signals, now) {
|
|
3034
|
+
const importance = getImportance(item);
|
|
3035
|
+
const ageDays = getAgeDays(item, now);
|
|
3036
|
+
let utility = item.score * 5 + importance * 0.7;
|
|
3037
|
+
if (category === "core") {
|
|
3038
|
+
utility += 10;
|
|
3039
|
+
} else if (category === "active") {
|
|
3040
|
+
utility += 6;
|
|
3041
|
+
} else if (category === "preferences") {
|
|
3042
|
+
utility += 4;
|
|
3043
|
+
} else {
|
|
3044
|
+
utility += 1;
|
|
3045
|
+
}
|
|
3046
|
+
if (signals.behaviorImpact) {
|
|
3047
|
+
utility += 3;
|
|
3048
|
+
}
|
|
3049
|
+
if (signals.exactState) {
|
|
3050
|
+
utility += 3;
|
|
3051
|
+
}
|
|
3052
|
+
if (signals.openThread) {
|
|
3053
|
+
utility += 4;
|
|
3054
|
+
}
|
|
3055
|
+
if (signals.operational) {
|
|
3056
|
+
utility += 2;
|
|
3057
|
+
}
|
|
3058
|
+
if (signals.veryRecent) {
|
|
3059
|
+
utility += 2;
|
|
3060
|
+
} else if (signals.fresh) {
|
|
3061
|
+
utility += 1;
|
|
3062
|
+
} else if (ageDays > 30) {
|
|
3063
|
+
utility -= 2;
|
|
3064
|
+
}
|
|
3065
|
+
if (signals.archival) {
|
|
3066
|
+
utility -= 3;
|
|
3067
|
+
}
|
|
3068
|
+
if (item.entry?.type === "relationship") {
|
|
3069
|
+
utility -= 1;
|
|
3070
|
+
}
|
|
3071
|
+
return utility;
|
|
3072
|
+
}
|
|
3073
|
+
function isRedundantWithRecentSession(item, recentSessionText, recentSessionTokens) {
|
|
3074
|
+
if (!recentSessionText) {
|
|
3075
|
+
return false;
|
|
3076
|
+
}
|
|
3077
|
+
const rawSubject = typeof item.entry?.subject === "string" ? item.entry.subject : "";
|
|
3078
|
+
const rawContent = typeof item.entry?.content === "string" ? item.entry.content : "";
|
|
3079
|
+
const subject = normalizeText(rawSubject);
|
|
3080
|
+
const content = normalizeText(rawContent);
|
|
3081
|
+
if (subject && recentSessionText.includes(subject)) {
|
|
3082
|
+
return true;
|
|
3083
|
+
}
|
|
3084
|
+
if (content.length >= 24 && recentSessionText.includes(content.slice(0, 160))) {
|
|
3085
|
+
return true;
|
|
3086
|
+
}
|
|
3087
|
+
const overlap = overlapRatio(`${subject} ${content}`, recentSessionTokens);
|
|
3088
|
+
if (subject.includes("session handoff") && overlap >= 0.45) {
|
|
3089
|
+
return true;
|
|
3090
|
+
}
|
|
3091
|
+
if (hasExactStateSignal(rawSubject, rawContent) && overlap >= 0.45) {
|
|
3092
|
+
return true;
|
|
3093
|
+
}
|
|
3094
|
+
return overlap >= 0.55;
|
|
3095
|
+
}
|
|
3096
|
+
function compareCandidates(a, b) {
|
|
3097
|
+
if (b.utility !== a.utility) {
|
|
3098
|
+
return b.utility - a.utility;
|
|
3099
|
+
}
|
|
3100
|
+
if (CATEGORY_PRIORITY[a.category] !== CATEGORY_PRIORITY[b.category]) {
|
|
3101
|
+
return CATEGORY_PRIORITY[a.category] - CATEGORY_PRIORITY[b.category];
|
|
3102
|
+
}
|
|
3103
|
+
return b.updatedAt - a.updatedAt;
|
|
3104
|
+
}
|
|
3105
|
+
function roundRobinOrder(groups, maxNonCoreEntries) {
|
|
3106
|
+
const ordered = [];
|
|
3107
|
+
const categories = ["active", "preferences", "recent"];
|
|
3108
|
+
let index = 0;
|
|
3109
|
+
while (ordered.length < maxNonCoreEntries) {
|
|
3110
|
+
let addedThisRound = false;
|
|
3111
|
+
for (const category of categories) {
|
|
3112
|
+
const candidate = groups[category][index];
|
|
3113
|
+
if (!candidate) {
|
|
3114
|
+
continue;
|
|
3115
|
+
}
|
|
3116
|
+
ordered.push(candidate);
|
|
3117
|
+
addedThisRound = true;
|
|
3118
|
+
if (ordered.length >= maxNonCoreEntries) {
|
|
3119
|
+
return ordered;
|
|
3120
|
+
}
|
|
3121
|
+
}
|
|
3122
|
+
if (!addedThisRound) {
|
|
3123
|
+
break;
|
|
3124
|
+
}
|
|
3125
|
+
index += 1;
|
|
3126
|
+
}
|
|
3127
|
+
return ordered;
|
|
3128
|
+
}
|
|
3129
|
+
function selectSessionStartMemory(coreResult, browseResult, options = {}) {
|
|
3130
|
+
const now = options.now?.getTime() ?? Date.now();
|
|
3131
|
+
const sectionCaps = {
|
|
3132
|
+
...DEFAULT_SESSION_START_SECTION_CAPS,
|
|
3133
|
+
...options.sectionCaps
|
|
3134
|
+
};
|
|
3135
|
+
const maxNonCoreEntries = options.maxNonCoreEntries ?? Number.POSITIVE_INFINITY;
|
|
3136
|
+
const recentSessionText = normalizeText(options.recentSessionText ?? "");
|
|
3137
|
+
const recentSessionTokens = new Set(tokenize(recentSessionText));
|
|
3138
|
+
const coreCandidates = [];
|
|
3139
|
+
const nonCoreCandidates = [];
|
|
3140
|
+
for (const item of coreResult?.results ?? []) {
|
|
3141
|
+
const signals = buildSignals(item, now);
|
|
3142
|
+
coreCandidates.push({
|
|
3143
|
+
item: {
|
|
3144
|
+
...item,
|
|
3145
|
+
category: "core"
|
|
3146
|
+
},
|
|
3147
|
+
category: "core",
|
|
3148
|
+
overlapKeys: buildOverlapKeys(item),
|
|
3149
|
+
utility: computeUtility(item, "core", signals, now),
|
|
3150
|
+
updatedAt: toTimestamp(item.entry?.updated_at)
|
|
3151
|
+
});
|
|
3152
|
+
}
|
|
3153
|
+
for (const item of browseResult?.results ?? []) {
|
|
3154
|
+
const signals = buildSignals(item, now);
|
|
3155
|
+
const category = classifyCandidate(item, signals, now);
|
|
3156
|
+
if (!category || category === "core") {
|
|
3157
|
+
continue;
|
|
3158
|
+
}
|
|
3159
|
+
if (isRedundantWithRecentSession(item, recentSessionText, recentSessionTokens)) {
|
|
3160
|
+
continue;
|
|
3161
|
+
}
|
|
3162
|
+
nonCoreCandidates.push({
|
|
3163
|
+
item: {
|
|
3164
|
+
...item,
|
|
3165
|
+
category
|
|
3166
|
+
},
|
|
3167
|
+
category,
|
|
3168
|
+
overlapKeys: buildOverlapKeys(item),
|
|
3169
|
+
utility: computeUtility(item, category, signals, now),
|
|
3170
|
+
updatedAt: toTimestamp(item.entry?.updated_at)
|
|
3171
|
+
});
|
|
3172
|
+
}
|
|
3173
|
+
coreCandidates.sort(compareCandidates);
|
|
3174
|
+
nonCoreCandidates.sort(compareCandidates);
|
|
3175
|
+
const seenOverlapKeys = /* @__PURE__ */ new Set();
|
|
3176
|
+
const selectedCore = [];
|
|
3177
|
+
for (const candidate of coreCandidates) {
|
|
3178
|
+
const overlaps = candidate.overlapKeys.some((key) => seenOverlapKeys.has(key));
|
|
3179
|
+
if (overlaps) {
|
|
3180
|
+
continue;
|
|
3181
|
+
}
|
|
3182
|
+
selectedCore.push(candidate);
|
|
3183
|
+
for (const key of candidate.overlapKeys) {
|
|
3184
|
+
seenOverlapKeys.add(key);
|
|
3185
|
+
}
|
|
3186
|
+
if (selectedCore.length >= sectionCaps.core) {
|
|
3187
|
+
break;
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
3190
|
+
const grouped = {
|
|
3191
|
+
core: selectedCore,
|
|
3192
|
+
active: [],
|
|
3193
|
+
preferences: [],
|
|
3194
|
+
recent: []
|
|
3195
|
+
};
|
|
3196
|
+
for (const candidate of nonCoreCandidates) {
|
|
3197
|
+
if (grouped[candidate.category].length >= sectionCaps[candidate.category]) {
|
|
3198
|
+
continue;
|
|
3199
|
+
}
|
|
3200
|
+
const overlaps = candidate.overlapKeys.some((key) => seenOverlapKeys.has(key));
|
|
3201
|
+
if (overlaps) {
|
|
3202
|
+
continue;
|
|
3203
|
+
}
|
|
3204
|
+
grouped[candidate.category].push(candidate);
|
|
3205
|
+
for (const key of candidate.overlapKeys) {
|
|
3206
|
+
seenOverlapKeys.add(key);
|
|
3207
|
+
}
|
|
3208
|
+
}
|
|
3209
|
+
const selectedNonCore = roundRobinOrder(grouped, maxNonCoreEntries);
|
|
3210
|
+
const results = [
|
|
3211
|
+
...grouped.core.map((candidate) => candidate.item),
|
|
3212
|
+
...grouped.active.filter((candidate) => selectedNonCore.includes(candidate)).map((candidate) => candidate.item),
|
|
3213
|
+
...grouped.preferences.filter((candidate) => selectedNonCore.includes(candidate)).map((candidate) => candidate.item),
|
|
3214
|
+
...grouped.recent.filter((candidate) => selectedNonCore.includes(candidate)).map((candidate) => candidate.item)
|
|
3215
|
+
];
|
|
3216
|
+
if (results.length === 0) {
|
|
3217
|
+
return null;
|
|
3218
|
+
}
|
|
3219
|
+
return {
|
|
3220
|
+
query: browseResult?.query ?? coreResult?.query ?? "[session-start]",
|
|
3221
|
+
results
|
|
3222
|
+
};
|
|
3223
|
+
}
|
|
3224
|
+
|
|
3225
|
+
// src/openclaw-plugin/session-start-browse.ts
|
|
3226
|
+
var SESSION_START_BROWSE_CANDIDATE_LIMIT = 10;
|
|
3227
|
+
var SESSION_START_BROWSE_MIN_IMPORTANCE = 7;
|
|
3228
|
+
var SESSION_START_BROWSE_PREFETCH_MULTIPLIER = 3;
|
|
3229
|
+
var SESSION_START_BROWSE_PREFETCH_LIMIT = SESSION_START_BROWSE_CANDIDATE_LIMIT * SESSION_START_BROWSE_PREFETCH_MULTIPLIER;
|
|
3230
|
+
var SESSION_START_BROWSE_TYPE_CAPS = {
|
|
3231
|
+
reflection: 3
|
|
3232
|
+
};
|
|
3233
|
+
function normalizeType(item) {
|
|
3234
|
+
const rawType = item.entry?.type;
|
|
3235
|
+
return typeof rawType === "string" ? rawType.trim().toLowerCase() : "";
|
|
3236
|
+
}
|
|
3237
|
+
function diversifySessionStartBrowseCandidates(browseResult, limit = SESSION_START_BROWSE_CANDIDATE_LIMIT, typeCaps = SESSION_START_BROWSE_TYPE_CAPS) {
|
|
3238
|
+
if (!browseResult) {
|
|
3239
|
+
return null;
|
|
3240
|
+
}
|
|
3241
|
+
const normalizedLimit = Math.max(0, Math.floor(limit));
|
|
3242
|
+
if (normalizedLimit === 0 || browseResult.results.length === 0) {
|
|
3243
|
+
return {
|
|
3244
|
+
...browseResult,
|
|
3245
|
+
results: []
|
|
3246
|
+
};
|
|
3247
|
+
}
|
|
3248
|
+
const admittedIndexes = /* @__PURE__ */ new Set();
|
|
3249
|
+
const counts = /* @__PURE__ */ new Map();
|
|
3250
|
+
const selected = [];
|
|
3251
|
+
for (const [index, item] of browseResult.results.entries()) {
|
|
3252
|
+
if (selected.length >= normalizedLimit) {
|
|
3253
|
+
break;
|
|
3254
|
+
}
|
|
3255
|
+
const type = normalizeType(item);
|
|
3256
|
+
const cap = typeCaps[type];
|
|
3257
|
+
const count = counts.get(type) ?? 0;
|
|
3258
|
+
if (cap !== void 0 && count >= cap) {
|
|
3259
|
+
continue;
|
|
3260
|
+
}
|
|
3261
|
+
selected.push(item);
|
|
3262
|
+
admittedIndexes.add(index);
|
|
3263
|
+
if (type) {
|
|
3264
|
+
counts.set(type, count + 1);
|
|
3265
|
+
}
|
|
3266
|
+
}
|
|
3267
|
+
if (selected.length < normalizedLimit) {
|
|
3268
|
+
for (const [index, item] of browseResult.results.entries()) {
|
|
3269
|
+
if (selected.length >= normalizedLimit) {
|
|
3270
|
+
break;
|
|
3271
|
+
}
|
|
3272
|
+
if (admittedIndexes.has(index)) {
|
|
3273
|
+
continue;
|
|
3274
|
+
}
|
|
3275
|
+
selected.push(item);
|
|
3276
|
+
}
|
|
3277
|
+
}
|
|
3278
|
+
return {
|
|
3279
|
+
...browseResult,
|
|
3280
|
+
results: selected
|
|
3281
|
+
};
|
|
3282
|
+
}
|
|
3283
|
+
|
|
2644
3284
|
// src/openclaw-plugin/heartbeat.ts
|
|
2645
3285
|
var HEARTBEAT_FILE_PATTERN = /\bHEARTBEAT\.md\b/i;
|
|
2646
3286
|
function isHeartbeatPoll(message) {
|
|
@@ -2764,10 +3404,14 @@ var DEFAULT_MID_SESSION_RECALL_MODE = "hybrid";
|
|
|
2764
3404
|
var DEFAULT_STORE_NUDGE_THRESHOLD = 8;
|
|
2765
3405
|
var DEFAULT_STORE_NUDGE_MAX_PER_SESSION = 5;
|
|
2766
3406
|
var DEFAULT_SESSION_START_BUDGET_CHARS = 12e3;
|
|
2767
|
-
var SESSION_START_BROWSE_LIMIT = 10;
|
|
2768
|
-
var SESSION_START_BROWSE_MIN_IMPORTANCE = 7;
|
|
2769
3407
|
var SESSION_START_CORE_MAX_CHARS = 4e3;
|
|
3408
|
+
var SESSION_START_MEMORY_INDEX_MAX_CHARS = 1200;
|
|
3409
|
+
var SESSION_START_NON_CORE_MAX_ENTRIES = DEFAULT_SESSION_START_SECTION_CAPS.active + DEFAULT_SESSION_START_SECTION_CAPS.preferences + DEFAULT_SESSION_START_SECTION_CAPS.recent;
|
|
3410
|
+
var SESSION_START_RECENT_HEADER = "### State and Continuity";
|
|
3411
|
+
var SESSION_START_RECENT_SESSION_MAX_CHARS = 1200;
|
|
3412
|
+
var SESSION_START_SECTION_TRUNCATION_NOTICE = "\n[Section truncated]";
|
|
2770
3413
|
var SESSION_START_TRUNCATION_NOTICE = "\n\n[Memory context truncated at budget]";
|
|
3414
|
+
var SESSION_START_MEMORY_BLOCK_HEADER = "## Recent memory\n";
|
|
2771
3415
|
var SESSION_START_FORMAT_OPTIONS = {
|
|
2772
3416
|
maxEntryChars: 500,
|
|
2773
3417
|
maxChars: 5e3
|
|
@@ -2838,21 +3482,32 @@ function getRecallEntryDedupKey(item) {
|
|
|
2838
3482
|
}
|
|
2839
3483
|
return `content:${type}\0${subject}\0${content}`;
|
|
2840
3484
|
}
|
|
2841
|
-
function
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
3485
|
+
function capSectionText(text, maxChars, notice = SESSION_START_SECTION_TRUNCATION_NOTICE) {
|
|
3486
|
+
if (!text || maxChars <= 0) {
|
|
3487
|
+
return "";
|
|
3488
|
+
}
|
|
3489
|
+
if (text.length <= maxChars) {
|
|
3490
|
+
return text;
|
|
3491
|
+
}
|
|
3492
|
+
if (maxChars <= notice.length) {
|
|
3493
|
+
return "";
|
|
3494
|
+
}
|
|
3495
|
+
return `${text.slice(0, maxChars - notice.length).trimEnd()}${notice}`;
|
|
3496
|
+
}
|
|
3497
|
+
function pushCappedSection(blocks, block, hardCapChars) {
|
|
3498
|
+
if (!block) {
|
|
3499
|
+
return;
|
|
3500
|
+
}
|
|
3501
|
+
const remaining = hardCapChars - blocks.reduce((sum, existing, index) => {
|
|
3502
|
+
return sum + existing.length + (index > 0 ? 2 : 0);
|
|
3503
|
+
}, 0) - (blocks.length > 0 ? 2 : 0);
|
|
3504
|
+
if (remaining <= 0) {
|
|
3505
|
+
return;
|
|
3506
|
+
}
|
|
3507
|
+
const capped = capSectionText(block, remaining, SESSION_START_TRUNCATION_NOTICE);
|
|
3508
|
+
if (capped) {
|
|
3509
|
+
blocks.push(capped);
|
|
2851
3510
|
}
|
|
2852
|
-
return {
|
|
2853
|
-
query: browseResult?.query ?? coreResult?.query ?? "[session-start]",
|
|
2854
|
-
results
|
|
2855
|
-
};
|
|
2856
3511
|
}
|
|
2857
3512
|
function formatProjectScope(projects) {
|
|
2858
3513
|
const normalizedProjects = Array.from(
|
|
@@ -3198,7 +3853,7 @@ async function handleBeforePromptBuild(event, ctx, params) {
|
|
|
3198
3853
|
const browseRecallOptions = {
|
|
3199
3854
|
context: "browse",
|
|
3200
3855
|
since: "30d",
|
|
3201
|
-
limit:
|
|
3856
|
+
limit: SESSION_START_BROWSE_PREFETCH_LIMIT,
|
|
3202
3857
|
minImportance: SESSION_START_BROWSE_MIN_IMPORTANCE
|
|
3203
3858
|
};
|
|
3204
3859
|
if (!sessionStartBrowseProject) {
|
|
@@ -3256,6 +3911,12 @@ async function handleBeforePromptBuild(event, ctx, params) {
|
|
|
3256
3911
|
return !key || !coreEntryKeys.has(key);
|
|
3257
3912
|
});
|
|
3258
3913
|
}
|
|
3914
|
+
if (browseResult) {
|
|
3915
|
+
browseResult.results = diversifySessionStartBrowseCandidates(
|
|
3916
|
+
browseResult,
|
|
3917
|
+
SESSION_START_BROWSE_CANDIDATE_LIMIT
|
|
3918
|
+
)?.results ?? [];
|
|
3919
|
+
}
|
|
3259
3920
|
if (browseResult) {
|
|
3260
3921
|
debugLog(
|
|
3261
3922
|
params.debug,
|
|
@@ -3308,48 +3969,75 @@ async function handleBeforePromptBuild(event, ctx, params) {
|
|
|
3308
3969
|
`filtered ${browseResult.results.length - browseResultForContext.results.length} session handoff entries after explicit clear`
|
|
3309
3970
|
);
|
|
3310
3971
|
}
|
|
3311
|
-
const
|
|
3972
|
+
const hardCapChars = resolveSessionStartBudgetChars(params.config);
|
|
3312
3973
|
const sections = [];
|
|
3313
3974
|
const sessionProjectSection = formatSessionProjectStateSection(sessionProjectResolution);
|
|
3314
3975
|
if (sessionProjectSection) {
|
|
3315
|
-
sections
|
|
3976
|
+
pushCappedSection(sections, sessionProjectSection, hardCapChars);
|
|
3316
3977
|
}
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3978
|
+
const recentSessionBody = capSectionText(
|
|
3979
|
+
previousTurns.trim(),
|
|
3980
|
+
SESSION_START_RECENT_SESSION_MAX_CHARS
|
|
3981
|
+
);
|
|
3982
|
+
if (recentSessionBody) {
|
|
3983
|
+
pushCappedSection(sections, `## Recent session
|
|
3984
|
+
${recentSessionBody}`, hardCapChars);
|
|
3320
3985
|
}
|
|
3986
|
+
const memoryIndexSection = capSectionText(
|
|
3987
|
+
memoryIndexMarkdown?.trim() ?? "",
|
|
3988
|
+
SESSION_START_MEMORY_INDEX_MAX_CHARS
|
|
3989
|
+
);
|
|
3990
|
+
const sessionStartMemory = selectSessionStartMemory(
|
|
3991
|
+
coreResult,
|
|
3992
|
+
browseResultForContext,
|
|
3993
|
+
{
|
|
3994
|
+
maxNonCoreEntries: SESSION_START_NON_CORE_MAX_ENTRIES,
|
|
3995
|
+
recentSessionText: recentSessionBody
|
|
3996
|
+
}
|
|
3997
|
+
);
|
|
3998
|
+
const renderedStartupItems = [];
|
|
3321
3999
|
if (sessionStartMemory) {
|
|
3322
|
-
const
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
4000
|
+
const reservedForMemoryIndex = memoryIndexSection ? memoryIndexSection.length + 2 : 0;
|
|
4001
|
+
const usedBeforeMemory = sections.reduce((sum, block, index) => {
|
|
4002
|
+
return sum + block.length + (index > 0 ? 2 : 0);
|
|
4003
|
+
}, 0);
|
|
4004
|
+
const availableForMemory = Math.max(
|
|
4005
|
+
0,
|
|
4006
|
+
hardCapChars - usedBeforeMemory - reservedForMemoryIndex - (sections.length > 0 ? 2 : 0)
|
|
4007
|
+
);
|
|
4008
|
+
const memoryBodyBudget = Math.max(0, availableForMemory - SESSION_START_MEMORY_BLOCK_HEADER.length);
|
|
4009
|
+
const renderOptions = {
|
|
4010
|
+
...SESSION_START_FORMAT_OPTIONS,
|
|
4011
|
+
maxChars: Math.min(SESSION_START_FORMAT_OPTIONS.maxChars ?? memoryBodyBudget, memoryBodyBudget),
|
|
4012
|
+
coreMaxChars: SESSION_START_CORE_MAX_CHARS,
|
|
4013
|
+
includeRootHeader: false,
|
|
4014
|
+
recentSectionHeader: SESSION_START_RECENT_HEADER
|
|
4015
|
+
};
|
|
4016
|
+
const formatted = formatRecallAsMarkdown(sessionStartMemory, renderOptions).trim();
|
|
4017
|
+
const renderRecallAsMarkdown2 = Reflect.get(
|
|
4018
|
+
recall_exports,
|
|
4019
|
+
"renderRecallAsMarkdown"
|
|
3329
4020
|
);
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
4021
|
+
const renderedMemory = typeof renderRecallAsMarkdown2 === "function" ? renderRecallAsMarkdown2(sessionStartMemory, renderOptions) : {
|
|
4022
|
+
markdown: formatted,
|
|
4023
|
+
renderedItems: formatted ? sessionStartMemory.results : []
|
|
4024
|
+
};
|
|
4025
|
+
if (formatted) {
|
|
4026
|
+
pushCappedSection(sections, `${SESSION_START_MEMORY_BLOCK_HEADER}${formatted}`, hardCapChars);
|
|
4027
|
+
renderedStartupItems.push(...renderedMemory.renderedItems);
|
|
3333
4028
|
}
|
|
3334
4029
|
}
|
|
3335
|
-
if (
|
|
3336
|
-
sections
|
|
4030
|
+
if (memoryIndexSection) {
|
|
4031
|
+
pushCappedSection(sections, memoryIndexSection, hardCapChars);
|
|
3337
4032
|
}
|
|
3338
4033
|
markdown = sections.length > 0 ? sections.join("\n\n") : void 0;
|
|
3339
|
-
const hardCapChars = resolveSessionStartBudgetChars(params.config);
|
|
3340
4034
|
if (markdown && markdown.length > hardCapChars) {
|
|
3341
4035
|
const retainedLength = Math.max(0, hardCapChars - SESSION_START_TRUNCATION_NOTICE.length);
|
|
3342
4036
|
markdown = `${markdown.slice(0, retainedLength)}${SESSION_START_TRUNCATION_NOTICE}`;
|
|
3343
4037
|
}
|
|
3344
4038
|
debugLog(params.debug, "session-start", `prependContext chars=${markdown?.length ?? 0}`);
|
|
3345
4039
|
const recalledIds = /* @__PURE__ */ new Set();
|
|
3346
|
-
for (const item of
|
|
3347
|
-
const id = getRecallEntryId(item);
|
|
3348
|
-
if (id) {
|
|
3349
|
-
recalledIds.add(id);
|
|
3350
|
-
}
|
|
3351
|
-
}
|
|
3352
|
-
for (const item of browseResultForContext?.results ?? []) {
|
|
4040
|
+
for (const item of renderedStartupItems) {
|
|
3353
4041
|
const id = getRecallEntryId(item);
|
|
3354
4042
|
if (id) {
|
|
3355
4043
|
recalledIds.add(id);
|