@joshuaswarren/openclaw-engram 9.0.107 → 9.0.108
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.
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// openclaw-engram: Local-first memory plugin
|
|
2
|
+
|
|
3
|
+
// src/version-utils.ts
|
|
4
|
+
function compareVersions(a, b) {
|
|
5
|
+
for (let i = 0; i < 3; i += 1) {
|
|
6
|
+
if (a[i] > b[i]) return 1;
|
|
7
|
+
if (a[i] < b[i]) return -1;
|
|
8
|
+
}
|
|
9
|
+
return 0;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
compareVersions
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=chunk-PKKCGNF2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/version-utils.ts"],"sourcesContent":["export type VersionTriple = readonly [number, number, number];\n\nexport function compareVersions(a: VersionTriple, b: VersionTriple): number {\n for (let i = 0; i < 3; i += 1) {\n if (a[i] > b[i]) return 1;\n if (a[i] < b[i]) return -1;\n }\n return 0;\n}\n"],"mappings":";;;AAEO,SAAS,gBAAgB,GAAkB,GAA0B;AAC1E,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK,GAAG;AAC7B,QAAI,EAAE,CAAC,IAAI,EAAE,CAAC,EAAG,QAAO;AACxB,QAAI,EAAE,CAAC,IAAI,EAAE,CAAC,EAAG,QAAO;AAAA,EAC1B;AACA,SAAO;AACT;","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
// openclaw-engram: Local-first memory plugin
|
|
2
|
+
import {
|
|
3
|
+
compareVersions
|
|
4
|
+
} from "./chunk-PKKCGNF2.js";
|
|
2
5
|
import {
|
|
3
6
|
EngramAccessInputError,
|
|
4
7
|
EngramAccessService,
|
|
@@ -127,6 +130,30 @@ import {
|
|
|
127
130
|
log
|
|
128
131
|
} from "./chunk-SSIIJJKA.js";
|
|
129
132
|
|
|
133
|
+
// src/index.ts
|
|
134
|
+
import { createRequire } from "module";
|
|
135
|
+
|
|
136
|
+
// src/sdk-compat.ts
|
|
137
|
+
function detectSdkCapabilities(api) {
|
|
138
|
+
const hasRegisterMemoryPromptSection = typeof api.registerMemoryPromptSection === "function";
|
|
139
|
+
const hasRuntimeNamespace = typeof api.runtime === "object" && api.runtime !== null;
|
|
140
|
+
const hasRegistrationMode = typeof api.registrationMode === "string";
|
|
141
|
+
const sdkVersion = (hasRuntimeNamespace && typeof api.runtime?.version === "string" ? api.runtime.version : null) ?? (typeof process?.env?.OPENCLAW_SERVICE_VERSION === "string" ? process.env.OPENCLAW_SERVICE_VERSION : null) ?? "legacy";
|
|
142
|
+
const isNewSdk = hasRegisterMemoryPromptSection || hasRuntimeNamespace || hasRegistrationMode;
|
|
143
|
+
const hasNewHookSystem = hasRegisterMemoryPromptSection || hasRegistrationMode;
|
|
144
|
+
return {
|
|
145
|
+
hasBeforePromptBuild: hasNewHookSystem,
|
|
146
|
+
hasRegisterMemoryPromptSection,
|
|
147
|
+
hasDefinePluginEntry: isNewSdk,
|
|
148
|
+
// entry point is less risky, keep broad detection
|
|
149
|
+
hasRuntimeNamespace,
|
|
150
|
+
hasRegistrationMode,
|
|
151
|
+
hasTypedHooks: hasNewHookSystem,
|
|
152
|
+
sdkVersion,
|
|
153
|
+
registrationMode: hasRegistrationMode ? api.registrationMode : void 0
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
130
157
|
// src/tools.ts
|
|
131
158
|
import { createHash } from "crypto";
|
|
132
159
|
import { Type } from "@sinclair/typebox";
|
|
@@ -8314,18 +8341,8 @@ var EngramAccessHttpServer = class {
|
|
|
8314
8341
|
import { access as access3, readFile as readFile16 } from "fs/promises";
|
|
8315
8342
|
import path23 from "path";
|
|
8316
8343
|
import { spawn as spawn2 } from "child_process";
|
|
8317
|
-
|
|
8318
|
-
|
|
8319
|
-
function compareVersions(a, b) {
|
|
8320
|
-
for (let i = 0; i < 3; i += 1) {
|
|
8321
|
-
if (a[i] > b[i]) return 1;
|
|
8322
|
-
if (a[i] < b[i]) return -1;
|
|
8323
|
-
}
|
|
8324
|
-
return 0;
|
|
8325
|
-
}
|
|
8326
|
-
|
|
8327
|
-
// src/compat/checks.ts
|
|
8328
|
-
var REQUIRED_HOOKS = ["before_agent_start", "agent_end"];
|
|
8344
|
+
var REQUIRED_HOOKS_LEGACY = ["before_agent_start", "agent_end"];
|
|
8345
|
+
var REQUIRED_HOOKS_NEW = ["before_prompt_build", "agent_end"];
|
|
8329
8346
|
function isSafeCommandToken(command) {
|
|
8330
8347
|
return /^[a-zA-Z0-9._-]+$/.test(command);
|
|
8331
8348
|
}
|
|
@@ -8627,7 +8644,12 @@ async function runCompatChecks(options) {
|
|
|
8627
8644
|
const indexRaw = await readFile16(indexPath, "utf-8");
|
|
8628
8645
|
const structuralSource = stripCommentsAndStrings(indexRaw);
|
|
8629
8646
|
const hooks = parseHookRegistrations(indexRaw);
|
|
8630
|
-
const
|
|
8647
|
+
const missingLegacy = REQUIRED_HOOKS_LEGACY.filter((hook) => !hooks.has(hook));
|
|
8648
|
+
const missingNew = REQUIRED_HOOKS_NEW.filter((hook) => !hooks.has(hook));
|
|
8649
|
+
const hasMemoryPromptSection = structuralSource.includes(".registerMemoryPromptSection(");
|
|
8650
|
+
const missingLegacyAdj = hasMemoryPromptSection ? missingLegacy.filter((h) => h !== "before_agent_start") : missingLegacy;
|
|
8651
|
+
const missingNewAdj = hasMemoryPromptSection ? missingNew.filter((h) => h !== "before_prompt_build") : missingNew;
|
|
8652
|
+
const missingHooks = missingNewAdj.length <= missingLegacyAdj.length ? missingNewAdj : missingLegacyAdj;
|
|
8631
8653
|
const hasGatewayStartHook = hooks.has("gateway_start");
|
|
8632
8654
|
const hasServiceStart = hasServiceStartRegistration(structuralSource);
|
|
8633
8655
|
if (missingHooks.length === 0 && (hasGatewayStartHook || hasServiceStart)) {
|
|
@@ -8650,7 +8672,7 @@ async function runCompatChecks(options) {
|
|
|
8650
8672
|
title: "Core hook registration",
|
|
8651
8673
|
level: "error",
|
|
8652
8674
|
message: `Missing expected registration(s): ${missingParts.join("; ")}`,
|
|
8653
|
-
remediation: "Ensure src/index.ts registers before_agent_start and agent_end, plus either gateway_start or api.registerService({ start })."
|
|
8675
|
+
remediation: "Ensure src/index.ts registers before_prompt_build (or before_agent_start) and agent_end, plus either gateway_start or api.registerService({ start })."
|
|
8654
8676
|
});
|
|
8655
8677
|
}
|
|
8656
8678
|
const cliWired = hasCliRegistration(structuralSource);
|
|
@@ -13117,34 +13139,6 @@ async function recordObjectiveStateSnapshotsFromAgentMessages(options) {
|
|
|
13117
13139
|
return { snapshots, filePaths };
|
|
13118
13140
|
}
|
|
13119
13141
|
|
|
13120
|
-
// src/legacy-hook-compat.ts
|
|
13121
|
-
var FIRST_PUBLISHED_RUNTIME_WITHOUT_AGENT_HEARTBEAT = [2026, 1, 29];
|
|
13122
|
-
var OPENCLAW_VERSION_PREFIX = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/;
|
|
13123
|
-
function parseOpenClawVersion(value) {
|
|
13124
|
-
if (typeof value !== "string") return null;
|
|
13125
|
-
const match = value.trim().match(OPENCLAW_VERSION_PREFIX);
|
|
13126
|
-
if (!match) return null;
|
|
13127
|
-
return {
|
|
13128
|
-
triple: [
|
|
13129
|
-
Number.parseInt(match[1], 10),
|
|
13130
|
-
Number.parseInt(match[2], 10),
|
|
13131
|
-
Number.parseInt(match[3], 10)
|
|
13132
|
-
],
|
|
13133
|
-
prerelease: typeof match[4] === "string" && match[4].length > 0
|
|
13134
|
-
};
|
|
13135
|
-
}
|
|
13136
|
-
function shouldRegisterTypedAgentHeartbeat(runtimeVersion) {
|
|
13137
|
-
const parsed = parseOpenClawVersion(runtimeVersion);
|
|
13138
|
-
if (!parsed) return false;
|
|
13139
|
-
const comparison = compareVersions(
|
|
13140
|
-
parsed.triple,
|
|
13141
|
-
FIRST_PUBLISHED_RUNTIME_WITHOUT_AGENT_HEARTBEAT
|
|
13142
|
-
);
|
|
13143
|
-
if (comparison < 0) return true;
|
|
13144
|
-
if (comparison > 0) return false;
|
|
13145
|
-
return parsed.prerelease;
|
|
13146
|
-
}
|
|
13147
|
-
|
|
13148
13142
|
// src/index.ts
|
|
13149
13143
|
import { readFile as readFile18, writeFile as writeFile12 } from "fs/promises";
|
|
13150
13144
|
import { readFileSync as readFileSync2 } from "fs";
|
|
@@ -13511,20 +13505,27 @@ var ENGRAM_ACCESS_SERVICE = "__openclawEngramAccessService";
|
|
|
13511
13505
|
var ENGRAM_ACCESS_HTTP_SERVER = "__openclawEngramAccessHttpServer";
|
|
13512
13506
|
var ENGRAM_SERVICE_STARTED = "__openclawEngramServiceStarted";
|
|
13513
13507
|
var ENGRAM_INIT_PROMISE = "__openclawEngramInitPromise";
|
|
13514
|
-
function
|
|
13508
|
+
function loadPluginEntryFromFile() {
|
|
13515
13509
|
try {
|
|
13516
13510
|
const explicitConfigPath = process.env.OPENCLAW_ENGRAM_CONFIG_PATH || process.env.OPENCLAW_CONFIG_PATH;
|
|
13517
13511
|
const homeDir = process.env.HOME ?? os3.homedir();
|
|
13518
13512
|
const configPath = explicitConfigPath && explicitConfigPath.length > 0 ? explicitConfigPath : path27.join(homeDir, ".openclaw", "openclaw.json");
|
|
13519
13513
|
const content = readFileSync2(configPath, "utf-8");
|
|
13520
13514
|
const config = JSON.parse(content);
|
|
13521
|
-
|
|
13522
|
-
return pluginEntry?.config;
|
|
13515
|
+
return config?.plugins?.entries?.["openclaw-engram"];
|
|
13523
13516
|
} catch (err) {
|
|
13524
13517
|
log.warn(`Failed to load config from file: ${err}`);
|
|
13525
13518
|
return void 0;
|
|
13526
13519
|
}
|
|
13527
13520
|
}
|
|
13521
|
+
function loadPluginConfigFromFile() {
|
|
13522
|
+
return loadPluginEntryFromFile()?.config;
|
|
13523
|
+
}
|
|
13524
|
+
function readPluginHooksPolicy(apiConfig) {
|
|
13525
|
+
const fromApi = apiConfig?.plugins?.entries?.["openclaw-engram"]?.hooks;
|
|
13526
|
+
if (fromApi && typeof fromApi === "object") return fromApi;
|
|
13527
|
+
return loadPluginEntryFromFile()?.hooks;
|
|
13528
|
+
}
|
|
13528
13529
|
function wildcardToRegExp(pattern) {
|
|
13529
13530
|
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
13530
13531
|
return new RegExp(`^${escaped.replace(/\*/g, ".*")}$`);
|
|
@@ -13543,20 +13544,31 @@ function shouldSkipRecallForSession(sessionKey, cfg) {
|
|
|
13543
13544
|
}
|
|
13544
13545
|
});
|
|
13545
13546
|
}
|
|
13546
|
-
function
|
|
13547
|
-
|
|
13548
|
-
|
|
13549
|
-
|
|
13550
|
-
|
|
13551
|
-
}
|
|
13547
|
+
function tryDefinePluginEntry(def) {
|
|
13548
|
+
try {
|
|
13549
|
+
const _require = createRequire(import.meta.url);
|
|
13550
|
+
const { definePluginEntry } = _require("openclaw/plugin-sdk/plugin-entry");
|
|
13551
|
+
return definePluginEntry(def);
|
|
13552
|
+
} catch {
|
|
13553
|
+
return def;
|
|
13554
|
+
}
|
|
13552
13555
|
}
|
|
13553
|
-
var
|
|
13556
|
+
var sdkCaps;
|
|
13557
|
+
var pluginDefinition = {
|
|
13554
13558
|
id: "openclaw-engram",
|
|
13555
13559
|
name: "Engram (Local Memory)",
|
|
13556
13560
|
description: "Local-first memory plugin. Uses GPT-5.2 for intelligent extraction and QMD for storage/retrieval.",
|
|
13557
13561
|
kind: "memory",
|
|
13558
13562
|
register(api) {
|
|
13559
13563
|
initLogger(api.logger, false);
|
|
13564
|
+
sdkCaps = detectSdkCapabilities(api);
|
|
13565
|
+
log.info(
|
|
13566
|
+
`SDK detection: version=${sdkCaps.sdkVersion}, beforePromptBuild=${sdkCaps.hasBeforePromptBuild}, memoryPromptSection=${sdkCaps.hasRegisterMemoryPromptSection}, typedHooks=${sdkCaps.hasTypedHooks}`
|
|
13567
|
+
);
|
|
13568
|
+
if (sdkCaps.registrationMode === "setup-only") {
|
|
13569
|
+
log.info("registrationMode=setup-only \u2014 skipping full initialization");
|
|
13570
|
+
return;
|
|
13571
|
+
}
|
|
13560
13572
|
const fileConfig = loadPluginConfigFromFile();
|
|
13561
13573
|
const cfg = parseConfig({
|
|
13562
13574
|
...api.pluginConfig,
|
|
@@ -13599,65 +13611,112 @@ var index_default = {
|
|
|
13599
13611
|
maxBodyBytes: cfg.agentAccessHttp.maxBodyBytes
|
|
13600
13612
|
});
|
|
13601
13613
|
globalThis[ENGRAM_ACCESS_HTTP_SERVER] = accessHttpServer;
|
|
13602
|
-
api.
|
|
13603
|
-
|
|
13604
|
-
|
|
13605
|
-
|
|
13606
|
-
|
|
13607
|
-
|
|
13608
|
-
|
|
13614
|
+
const hooksPolicy = readPluginHooksPolicy(api.config);
|
|
13615
|
+
const promptInjectionAllowed = hooksPolicy?.allowPromptInjection !== false;
|
|
13616
|
+
const useMemoryPromptSection = sdkCaps.hasRegisterMemoryPromptSection && typeof api.registerMemoryPromptSection === "function" && promptInjectionAllowed;
|
|
13617
|
+
async function recallHookHandler(hookLabel, event, ctx) {
|
|
13618
|
+
let prompt = event.prompt;
|
|
13619
|
+
if ((!prompt || prompt.length < 5) && Array.isArray(event.messages)) {
|
|
13620
|
+
const msgs = event.messages;
|
|
13621
|
+
for (let i = msgs.length - 1; i >= 0; i--) {
|
|
13622
|
+
if (msgs[i]?.role === "user") {
|
|
13623
|
+
const text = extractTextContent2(msgs[i]);
|
|
13624
|
+
if (text.length >= 5) {
|
|
13625
|
+
prompt = text;
|
|
13626
|
+
break;
|
|
13627
|
+
}
|
|
13628
|
+
}
|
|
13629
|
+
}
|
|
13630
|
+
}
|
|
13631
|
+
if (!prompt || prompt.length < 5) return;
|
|
13632
|
+
const sessionKey = ctx?.sessionKey ?? "default";
|
|
13633
|
+
log.debug(`${hookLabel}: sessionKey=${sessionKey}, promptLen=${prompt.length}`);
|
|
13634
|
+
log.debug(
|
|
13635
|
+
`${hookLabel}: cronRecallMode=${cfg.cronRecallMode}, allowlistCount=${cfg.cronRecallAllowlist.length}`
|
|
13636
|
+
);
|
|
13637
|
+
if (sessionKey.includes(":cron:") && cfg.cronRecallMode === "allowlist") {
|
|
13638
|
+
const matchedPattern = cfg.cronRecallAllowlist.find((pattern) => {
|
|
13639
|
+
const re = wildcardToRegExp(pattern);
|
|
13640
|
+
return re.test(sessionKey);
|
|
13641
|
+
});
|
|
13609
13642
|
log.debug(
|
|
13610
|
-
|
|
13643
|
+
`${hookLabel}: cron allowlist match=${matchedPattern ? "yes" : "no"} pattern=${matchedPattern ?? "none"}`
|
|
13611
13644
|
);
|
|
13612
|
-
|
|
13613
|
-
|
|
13614
|
-
|
|
13615
|
-
|
|
13616
|
-
|
|
13617
|
-
|
|
13618
|
-
|
|
13619
|
-
|
|
13645
|
+
}
|
|
13646
|
+
if (shouldSkipRecallForSession(sessionKey, cfg)) {
|
|
13647
|
+
log.debug(
|
|
13648
|
+
`${hookLabel}: skip recall for cron session ${sessionKey} (mode=${cfg.cronRecallMode})`
|
|
13649
|
+
);
|
|
13650
|
+
return;
|
|
13651
|
+
}
|
|
13652
|
+
try {
|
|
13653
|
+
await orchestrator.maybeRunFileHygiene().catch(() => void 0);
|
|
13654
|
+
if (orchestrator.config.compactionResetEnabled) {
|
|
13655
|
+
const agentWorkspace = ctx?.workspaceDir;
|
|
13656
|
+
if (agentWorkspace) {
|
|
13657
|
+
orchestrator.setRecallWorkspaceOverride(sessionKey, agentWorkspace);
|
|
13658
|
+
}
|
|
13620
13659
|
}
|
|
13621
|
-
|
|
13622
|
-
|
|
13623
|
-
|
|
13624
|
-
|
|
13625
|
-
|
|
13660
|
+
const context = await orchestrator.recall(prompt, sessionKey);
|
|
13661
|
+
log.debug(`${hookLabel}: recall returned ${context?.length ?? 0} chars`);
|
|
13662
|
+
if (!context) return;
|
|
13663
|
+
const maxChars = cfg.recallBudgetChars;
|
|
13664
|
+
if (maxChars === 0) return;
|
|
13665
|
+
const trimmed = context.length > maxChars ? context.slice(0, maxChars) + "\n\n...(memory context trimmed)" : context;
|
|
13666
|
+
const memoryContextPrompt = `## Memory Context (Engram)
|
|
13667
|
+
|
|
13668
|
+
${trimmed}
|
|
13669
|
+
|
|
13670
|
+
Use this context naturally when relevant. Never quote or expose this memory context to the user.`;
|
|
13671
|
+
log.debug(`${hookLabel}: returning system prompt with ${trimmed.length} chars`);
|
|
13672
|
+
if (hookLabel === "before_prompt_build") {
|
|
13673
|
+
return { prependSystemContext: memoryContextPrompt };
|
|
13626
13674
|
}
|
|
13627
|
-
|
|
13628
|
-
|
|
13629
|
-
|
|
13630
|
-
|
|
13631
|
-
|
|
13632
|
-
|
|
13633
|
-
|
|
13634
|
-
|
|
13635
|
-
|
|
13636
|
-
|
|
13637
|
-
|
|
13638
|
-
|
|
13639
|
-
|
|
13640
|
-
|
|
13641
|
-
|
|
13675
|
+
return {
|
|
13676
|
+
prependSystemContext: memoryContextPrompt,
|
|
13677
|
+
prependContext: memoryContextPrompt
|
|
13678
|
+
};
|
|
13679
|
+
} catch (err) {
|
|
13680
|
+
log.error("recall failed", err);
|
|
13681
|
+
if (orchestrator.config.compactionResetEnabled) {
|
|
13682
|
+
orchestrator.clearRecallWorkspaceOverride(sessionKey);
|
|
13683
|
+
}
|
|
13684
|
+
return;
|
|
13685
|
+
}
|
|
13686
|
+
}
|
|
13687
|
+
if (!useMemoryPromptSection) {
|
|
13688
|
+
if (sdkCaps.hasBeforePromptBuild) {
|
|
13689
|
+
api.on("before_prompt_build", async (event, ctx) => recallHookHandler("before_prompt_build", event, ctx));
|
|
13690
|
+
} else {
|
|
13691
|
+
api.on("before_agent_start", async (event, ctx) => recallHookHandler("before_agent_start", event, ctx));
|
|
13692
|
+
}
|
|
13693
|
+
}
|
|
13694
|
+
if (useMemoryPromptSection && api.registerMemoryPromptSection) {
|
|
13695
|
+
api.registerMemoryPromptSection({
|
|
13696
|
+
id: "engram-memory",
|
|
13697
|
+
label: "Engram Memory Context",
|
|
13698
|
+
build: async ({ prompt, sessionKey }) => {
|
|
13699
|
+
if (!prompt || prompt.length < 5) return null;
|
|
13700
|
+
if (shouldSkipRecallForSession(sessionKey, cfg)) return null;
|
|
13701
|
+
try {
|
|
13702
|
+
await orchestrator.maybeRunFileHygiene().catch(() => void 0);
|
|
13703
|
+
const context = await orchestrator.recall(prompt, sessionKey);
|
|
13704
|
+
if (!context) return null;
|
|
13705
|
+
const maxChars = cfg.recallBudgetChars;
|
|
13706
|
+
if (maxChars === 0) return null;
|
|
13707
|
+
const trimmed = context.length > maxChars ? context.slice(0, maxChars) + "\n\n...(memory context trimmed)" : context;
|
|
13708
|
+
return `## Memory Context (Engram)
|
|
13642
13709
|
|
|
13643
13710
|
${trimmed}
|
|
13644
13711
|
|
|
13645
13712
|
Use this context naturally when relevant. Never quote or expose this memory context to the user.`;
|
|
13646
|
-
|
|
13647
|
-
|
|
13648
|
-
|
|
13649
|
-
// Backward-compat path for gateway builds that consume prependContext.
|
|
13650
|
-
prependContext: memoryContextPrompt
|
|
13651
|
-
};
|
|
13652
|
-
} catch (err) {
|
|
13653
|
-
log.error("recall failed", err);
|
|
13654
|
-
if (orchestrator.config.compactionResetEnabled) {
|
|
13655
|
-
orchestrator.clearRecallWorkspaceOverride(sessionKey);
|
|
13713
|
+
} catch (err) {
|
|
13714
|
+
log.error("registerMemoryPromptSection build failed", err);
|
|
13715
|
+
return null;
|
|
13656
13716
|
}
|
|
13657
|
-
return;
|
|
13658
13717
|
}
|
|
13659
|
-
}
|
|
13660
|
-
|
|
13718
|
+
});
|
|
13719
|
+
}
|
|
13661
13720
|
api.on(
|
|
13662
13721
|
"agent_end",
|
|
13663
13722
|
async (event, ctx) => {
|
|
@@ -13772,30 +13831,6 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
13772
13831
|
}
|
|
13773
13832
|
}
|
|
13774
13833
|
);
|
|
13775
|
-
const runtimeApi = api;
|
|
13776
|
-
runtimeApi.registerHook?.(
|
|
13777
|
-
["agent_heartbeat", "agent:heartbeat"],
|
|
13778
|
-
(event) => {
|
|
13779
|
-
observeSessionHeartbeat(orchestrator, event.context);
|
|
13780
|
-
},
|
|
13781
|
-
{
|
|
13782
|
-
name: "engram_agent_heartbeat_legacy",
|
|
13783
|
-
description: "Observe legacy OpenClaw heartbeat events when the runtime still emits them."
|
|
13784
|
-
}
|
|
13785
|
-
);
|
|
13786
|
-
const runtimeVersion = runtimeApi.runtime?.version || process.env.OPENCLAW_SERVICE_VERSION || "unknown";
|
|
13787
|
-
if (shouldRegisterTypedAgentHeartbeat(runtimeVersion)) {
|
|
13788
|
-
api.on("agent_heartbeat", (_event, ctx) => {
|
|
13789
|
-
observeSessionHeartbeat(orchestrator, ctx);
|
|
13790
|
-
});
|
|
13791
|
-
log.info(
|
|
13792
|
-
`registered legacy typed agent_heartbeat hook for OpenClaw ${runtimeVersion}`
|
|
13793
|
-
);
|
|
13794
|
-
} else {
|
|
13795
|
-
log.debug(
|
|
13796
|
-
`skipping legacy typed agent_heartbeat hook for OpenClaw ${runtimeVersion}; published builds from 2026.1.29 onward do not expose it`
|
|
13797
|
-
);
|
|
13798
|
-
}
|
|
13799
13834
|
const lcmTokensBefore = /* @__PURE__ */ new Map();
|
|
13800
13835
|
api.on(
|
|
13801
13836
|
"before_compaction",
|
|
@@ -13848,7 +13883,7 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
13848
13883
|
api.on(
|
|
13849
13884
|
"after_compaction",
|
|
13850
13885
|
async (event, ctx) => {
|
|
13851
|
-
const sessionKey = ctx?.sessionKey ?? "default";
|
|
13886
|
+
const sessionKey = ctx?.sessionKey ?? event?.sessionKey ?? "default";
|
|
13852
13887
|
try {
|
|
13853
13888
|
if (orchestrator.lcmEngine?.enabled) {
|
|
13854
13889
|
try {
|
|
@@ -13919,6 +13954,98 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
13919
13954
|
}
|
|
13920
13955
|
}
|
|
13921
13956
|
);
|
|
13957
|
+
if (sdkCaps.hasBeforePromptBuild) {
|
|
13958
|
+
api.on(
|
|
13959
|
+
"session_start",
|
|
13960
|
+
async (event, _ctx) => {
|
|
13961
|
+
const sessionKey = event.sessionKey ?? "default";
|
|
13962
|
+
log.debug(`session_start: ${sessionKey}`);
|
|
13963
|
+
try {
|
|
13964
|
+
await orchestrator.maybeRunFileHygiene().catch(() => void 0);
|
|
13965
|
+
} catch (err) {
|
|
13966
|
+
log.debug(`session_start file hygiene failed: ${err}`);
|
|
13967
|
+
}
|
|
13968
|
+
}
|
|
13969
|
+
);
|
|
13970
|
+
api.on(
|
|
13971
|
+
"session_end",
|
|
13972
|
+
async (event, _ctx) => {
|
|
13973
|
+
const sessionKey = event.sessionKey ?? "default";
|
|
13974
|
+
log.debug(`session_end: ${sessionKey}`);
|
|
13975
|
+
if (orchestrator.config.compactionResetEnabled) {
|
|
13976
|
+
orchestrator.clearRecallWorkspaceOverride(sessionKey);
|
|
13977
|
+
}
|
|
13978
|
+
}
|
|
13979
|
+
);
|
|
13980
|
+
api.on(
|
|
13981
|
+
"before_tool_call",
|
|
13982
|
+
async (event, _ctx) => {
|
|
13983
|
+
if (event.toolName) {
|
|
13984
|
+
log.debug(`before_tool_call: ${event.toolName}`);
|
|
13985
|
+
}
|
|
13986
|
+
}
|
|
13987
|
+
);
|
|
13988
|
+
api.on(
|
|
13989
|
+
"after_tool_call",
|
|
13990
|
+
async (event, _ctx) => {
|
|
13991
|
+
if (event.toolName) {
|
|
13992
|
+
log.debug(`after_tool_call: ${event.toolName} (${event.durationMs ?? "?"}ms)`);
|
|
13993
|
+
}
|
|
13994
|
+
}
|
|
13995
|
+
);
|
|
13996
|
+
api.on(
|
|
13997
|
+
"llm_output",
|
|
13998
|
+
async (event, ctx) => {
|
|
13999
|
+
const sessionKey = ctx?.sessionKey ?? "default";
|
|
14000
|
+
if (event.tokenUsage) {
|
|
14001
|
+
log.debug(
|
|
14002
|
+
`llm_output: model=${event.model ?? "?"}, tokens=${event.tokenUsage.input ?? 0}/${event.tokenUsage.output ?? 0}, ${event.durationMs ?? "?"}ms, session=${sessionKey}`
|
|
14003
|
+
);
|
|
14004
|
+
}
|
|
14005
|
+
}
|
|
14006
|
+
);
|
|
14007
|
+
api.on(
|
|
14008
|
+
"subagent_spawning",
|
|
14009
|
+
async (event, _ctx) => {
|
|
14010
|
+
log.debug(`subagent_spawning: ${event.subagentId ?? "?"} purpose=${event.purpose ?? "?"}`);
|
|
14011
|
+
}
|
|
14012
|
+
);
|
|
14013
|
+
api.on(
|
|
14014
|
+
"subagent_ended",
|
|
14015
|
+
async (event, _ctx) => {
|
|
14016
|
+
log.debug(
|
|
14017
|
+
`subagent_ended: ${event.subagentId ?? "?"} success=${event.success ?? "?"} ${event.durationMs ?? "?"}ms`
|
|
14018
|
+
);
|
|
14019
|
+
}
|
|
14020
|
+
);
|
|
14021
|
+
} else {
|
|
14022
|
+
const runtimeApi = api;
|
|
14023
|
+
runtimeApi.registerHook?.(
|
|
14024
|
+
["agent_heartbeat", "agent:heartbeat"],
|
|
14025
|
+
(event) => {
|
|
14026
|
+
if (orchestrator.config.sessionObserverEnabled !== true) return;
|
|
14027
|
+
const sessionKey = event?.context?.sessionKey ?? "default";
|
|
14028
|
+
void orchestrator.observeSessionHeartbeat(sessionKey).catch((err) => {
|
|
14029
|
+
log.debug(`agent_heartbeat observer failed: ${err}`);
|
|
14030
|
+
});
|
|
14031
|
+
},
|
|
14032
|
+
{ name: "engram_agent_heartbeat_legacy", description: "Observe legacy heartbeat events for session observation." }
|
|
14033
|
+
);
|
|
14034
|
+
const runtimeVersion = runtimeApi.runtime?.version || process.env.OPENCLAW_SERVICE_VERSION || "unknown";
|
|
14035
|
+
void import("./legacy-hook-compat-722BG5XG.js").then(({ shouldRegisterTypedAgentHeartbeat }) => {
|
|
14036
|
+
if (shouldRegisterTypedAgentHeartbeat(runtimeVersion)) {
|
|
14037
|
+
api.on("agent_heartbeat", (_event, ctx) => {
|
|
14038
|
+
if (orchestrator.config.sessionObserverEnabled !== true) return;
|
|
14039
|
+
const sessionKey = ctx?.sessionKey ?? "default";
|
|
14040
|
+
void orchestrator.observeSessionHeartbeat(sessionKey).catch((err) => {
|
|
14041
|
+
log.debug(`agent_heartbeat typed observer failed: ${err}`);
|
|
14042
|
+
});
|
|
14043
|
+
});
|
|
14044
|
+
log.info(`registered typed agent_heartbeat hook for OpenClaw ${runtimeVersion}`);
|
|
14045
|
+
}
|
|
14046
|
+
}).catch(() => {
|
|
14047
|
+
});
|
|
14048
|
+
}
|
|
13922
14049
|
async function ensureHourlySummaryCron(api2) {
|
|
13923
14050
|
const jobId = "engram-hourly-summary";
|
|
13924
14051
|
const cronFilePath = path27.join(os3.homedir(), ".openclaw", "cron", "jobs.json");
|
|
@@ -14086,6 +14213,7 @@ Use this context naturally when relevant. Never quote or expose this memory cont
|
|
|
14086
14213
|
});
|
|
14087
14214
|
}
|
|
14088
14215
|
};
|
|
14216
|
+
var index_default = tryDefinePluginEntry(pluginDefinition);
|
|
14089
14217
|
function extractLastTurn(messages) {
|
|
14090
14218
|
let lastUserIdx = -1;
|
|
14091
14219
|
for (let i = messages.length - 1; i >= 0; i--) {
|