@xdarkicex/openclaw-memory-libravdb 1.10.10 → 1.10.14-beta.1

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/dist/index.js CHANGED
@@ -18615,10 +18615,10 @@ function buildRulesContext() {
18615
18615
  }
18616
18616
 
18617
18617
  // src/manifest.ts
18618
- import * as fs2 from "fs";
18619
- import * as path2 from "path";
18620
- import * as crypto from "crypto";
18621
- import * as os2 from "os";
18618
+ import * as fs2 from "node:fs";
18619
+ import * as path2 from "node:path";
18620
+ import * as crypto from "node:crypto";
18621
+ import * as os2 from "node:os";
18622
18622
  var TurnManifestStore = class {
18623
18623
  manifestDir;
18624
18624
  constructor() {
@@ -18923,7 +18923,7 @@ function normalizeCompactResult(response, options = {}) {
18923
18923
  const threshold = options.threshold;
18924
18924
  const overBudget = threshold != null && tokensBefore >= threshold;
18925
18925
  const engineRefused = !didCompact && overBudget;
18926
- const tokensAfter = didCompact && typeof response?.tokensAfter === "number" && response.tokensAfter > 0 ? response.tokensAfter : void 0;
18926
+ const tokensAfter = didCompact && typeof response?.tokensAfter === "number" && response.tokensAfter >= 0 ? response.tokensAfter : void 0;
18927
18927
  return {
18928
18928
  ok: !engineRefused,
18929
18929
  compacted: didCompact,
@@ -18931,8 +18931,7 @@ function normalizeCompactResult(response, options = {}) {
18931
18931
  result: {
18932
18932
  tokensBefore,
18933
18933
  ...tokensAfter != null ? { tokensAfter } : {},
18934
- ...details.summaryMethod ? { summary: details.summaryMethod } : {},
18935
- ...details.summaryText ? { summaryText: details.summaryText } : {},
18934
+ ...details.summaryText ? { summary: details.summaryText } : {},
18936
18935
  details: { ...details, ...threshold != null ? { threshold } : {} }
18937
18936
  }
18938
18937
  };
@@ -19011,6 +19010,18 @@ function findLastUserMessageIndex(messages) {
19011
19010
  }
19012
19011
  return -1;
19013
19012
  }
19013
+ function selectTurnAlignedSourceSuffix(messages, maxMessages) {
19014
+ if (messages.length === 0) return [];
19015
+ let start = Math.max(0, messages.length - Math.max(1, Math.floor(maxMessages)));
19016
+ while (start > 0 && messages[start]?.role !== "user") {
19017
+ start -= 1;
19018
+ }
19019
+ if (messages[start]?.role !== "user") {
19020
+ const firstUserIndex = messages.findIndex((message) => message.role === "user");
19021
+ start = firstUserIndex >= 0 ? firstUserIndex : 0;
19022
+ }
19023
+ return messages.slice(start);
19024
+ }
19014
19025
  function normalizeKernelContent(content, options = {}) {
19015
19026
  const text = typeof content === "string" ? content : Array.isArray(content) ? content.map(stringifyKernelBlock).filter((part) => part.length > 0).join("\n") : "";
19016
19027
  return stripOpenClawUntrustedMetadataEnvelope(text, {
@@ -19374,6 +19385,32 @@ function trimMessagesToBudget(messages, tokenBudget) {
19374
19385
  }
19375
19386
  return [{ ...last, content: truncated }];
19376
19387
  }
19388
+ function trimTurnAlignedSourceSuffixToBudget(messages, tokenBudget) {
19389
+ if (messages.length === 0) return [];
19390
+ const userStarts = [];
19391
+ for (let index = 0; index < messages.length; index += 1) {
19392
+ if (messages[index]?.role === "user") userStarts.push(index);
19393
+ }
19394
+ if (userStarts.length === 0) {
19395
+ return messages;
19396
+ }
19397
+ const keptBundles = [];
19398
+ let used = 0;
19399
+ let bundleEnd = messages.length;
19400
+ for (let index = userStarts.length - 1; index >= 0; index -= 1) {
19401
+ const bundleStart = userStarts[index];
19402
+ const bundle = messages.slice(bundleStart, bundleEnd);
19403
+ const bundleCost = approximateMessagesTokens(bundle);
19404
+ if (keptBundles.length > 0 && used + bundleCost > tokenBudget) {
19405
+ break;
19406
+ }
19407
+ keptBundles.unshift(bundle);
19408
+ used += bundleCost;
19409
+ bundleEnd = bundleStart;
19410
+ if (used >= tokenBudget) break;
19411
+ }
19412
+ return keptBundles.flat();
19413
+ }
19377
19414
  function boundAfterTurnMessagesForIngest(messages, logger, sessionId) {
19378
19415
  const estimatedTokens = approximateMessagesTokens(messages);
19379
19416
  if (estimatedTokens <= AFTER_TURN_INGEST_MAX_TOKENS) {
@@ -19414,6 +19451,34 @@ function enforceTokenBudgetInvariant(result, tokenBudget) {
19414
19451
  estimatedTokens: Math.min(effectiveBudget, systemPromptTokens + trimmedEstimate)
19415
19452
  };
19416
19453
  }
19454
+ function enforceCompactedProjectionBudgetInvariant(result, tokenBudget) {
19455
+ if (typeof tokenBudget !== "number" || !Number.isFinite(tokenBudget) || tokenBudget <= 0) {
19456
+ return result;
19457
+ }
19458
+ const effectiveBudget = resolveEffectiveAssembleBudget(Math.max(1, Math.floor(tokenBudget)));
19459
+ const approximateTotal = approximateTokenCount(result.systemPromptAddition) + approximateMessagesTokens(result.messages);
19460
+ if (result.estimatedTokens <= effectiveBudget && approximateTotal <= effectiveBudget) {
19461
+ return result;
19462
+ }
19463
+ const lastUserIndex = findLastUserMessageIndex(result.messages);
19464
+ const mandatoryMessages = lastUserIndex >= 0 ? result.messages.slice(lastUserIndex) : result.messages;
19465
+ const mandatoryTokens = approximateMessagesTokens(mandatoryMessages);
19466
+ const systemPromptBudget = Math.max(0, effectiveBudget - mandatoryTokens);
19467
+ const systemPromptAddition = truncateSystemPromptAdditionToTokenBudget(
19468
+ result.systemPromptAddition,
19469
+ systemPromptBudget
19470
+ );
19471
+ const systemPromptTokens = approximateTokenCount(systemPromptAddition);
19472
+ const messageBudget = Math.max(mandatoryTokens, effectiveBudget - systemPromptTokens);
19473
+ const messages = trimTurnAlignedSourceSuffixToBudget(result.messages, messageBudget);
19474
+ const estimatedTokens = systemPromptTokens + approximateMessagesTokens(messages);
19475
+ return {
19476
+ ...result,
19477
+ messages,
19478
+ systemPromptAddition,
19479
+ estimatedTokens
19480
+ };
19481
+ }
19417
19482
  function buildBudgetFallbackContext(messages, tokenBudget) {
19418
19483
  const effectiveBudget = resolveEffectiveAssembleBudget(tokenBudget);
19419
19484
  const fallbackMessages = trimMessagesToBudget(
@@ -19430,7 +19495,11 @@ function buildBudgetFallbackContext(messages, tokenBudget) {
19430
19495
  var DAEMON_AUTHORED_CONTEXT_RE = /<authored_context\b[^>]*>([\s\S]*?)<\/authored_context>/gi;
19431
19496
  var DAEMON_AUTHORED_CONTEXT_GUIDANCE_RE = /^\s*Treat the authored entries below as active project rules and identity context\.?\s*$/i;
19432
19497
  var COMPACTED_SESSION_CONTEXT_RE = /<compacted_session_context\b([^>]*)>([\s\S]*?)<\/compacted_session_context>/gi;
19498
+ var HAS_COMPACTED_SESSION_CONTEXT_RE = /<compacted_session_context\b/i;
19433
19499
  var COMPACTED_SESSION_RENDER_LEDGER_RE = /(?:^|\n)(?:Artifacts:|Constraints:|Open Next Steps:|Extracted context anchors:)(?:\n|$)/;
19500
+ function hasCompactedSessionContext(text) {
19501
+ return HAS_COMPACTED_SESSION_CONTEXT_RE.test(text);
19502
+ }
19434
19503
  function sanitizeDaemonSystemPromptAddition(text) {
19435
19504
  return demoteDaemonAuthoredContextBlocks(
19436
19505
  sanitizeToolCallPatterns(canonicalizeCompactedSessionContextBlocks(text))
@@ -19781,13 +19850,13 @@ function ensureReplaySafeUserTurn(assembled, sourceMessages, logger, tokenBudget
19781
19850
  estimatedTokens: baseEstimatedTokens + approximateMessageTokens(fallbackUser)
19782
19851
  };
19783
19852
  }
19784
- function normalizeAssembleResult(result, sourceMessages) {
19853
+ function normalizeAssembleResult(result, sourceMessages, promptAuthority = PROMPT_AUTHORITY_PREASSEMBLY_MAY_OVERFLOW) {
19785
19854
  const systemPromptAddition = typeof result.systemPromptAddition === "string" ? sanitizeDaemonSystemPromptAddition(result.systemPromptAddition) : "";
19786
19855
  return {
19787
19856
  messages: sourceMessages ?? [],
19788
19857
  estimatedTokens: typeof result.estimatedTokens === "number" ? result.estimatedTokens : 0,
19789
19858
  systemPromptAddition,
19790
- promptAuthority: PROMPT_AUTHORITY_PREASSEMBLY_MAY_OVERFLOW,
19859
+ promptAuthority,
19791
19860
  ...result.debug != null ? { debug: result.debug } : {}
19792
19861
  };
19793
19862
  }
@@ -19858,6 +19927,7 @@ function buildContextEngineFactory(runtime, cfg, logger = console) {
19858
19927
  setOptimizationMemoCacheSize(cfg.optimizationMemoCacheSize);
19859
19928
  }
19860
19929
  const predictiveContextCache = /* @__PURE__ */ new Map();
19930
+ const compactedProjectionSessions = /* @__PURE__ */ new Set();
19861
19931
  const PREDICTIVE_CACHE_MAX_SIZE = 100;
19862
19932
  const turnCache = new TurnMemoryCache(100);
19863
19933
  const circuitBreakers = /* @__PURE__ */ new Map();
@@ -19865,6 +19935,21 @@ function buildContextEngineFactory(runtime, cfg, logger = console) {
19865
19935
  let lastUserMessageHash = null;
19866
19936
  let cachedIdentity = null;
19867
19937
  let cachedSessionKey;
19938
+ function activateCompactedProjection(sessionId, source) {
19939
+ const wasActive = compactedProjectionSessions.has(sessionId);
19940
+ compactedProjectionSessions.add(sessionId);
19941
+ predictiveContextCache.delete(sessionId);
19942
+ postToolRecallCache.delete(sessionId);
19943
+ turnCache.invalidateSession(sessionId);
19944
+ if (!wasActive) {
19945
+ logger.info?.(
19946
+ `LibraVDB activated daemon-owned compacted projection sessionId=${sessionId} source=${source}`
19947
+ );
19948
+ }
19949
+ }
19950
+ function enforceAssembleBudget(result, tokenBudget, compactionProjectionActive) {
19951
+ return compactionProjectionActive ? enforceCompactedProjectionBudgetInvariant(result, tokenBudget) : enforceTokenBudgetInvariant(result, tokenBudget);
19952
+ }
19868
19953
  function resolveUserId(args) {
19869
19954
  const fwUserId = args?.userIdOverride?.trim();
19870
19955
  if (fwUserId) return fwUserId;
@@ -20281,16 +20366,36 @@ ${cached}
20281
20366
  return null;
20282
20367
  }
20283
20368
  }
20369
+ async function injectPersonaContext(params) {
20370
+ try {
20371
+ const resp = await params.client.getUserCard({ userId: "__bot_persona__" });
20372
+ if (!resp.cardJson) return null;
20373
+ let card;
20374
+ try {
20375
+ card = JSON.parse(resp.cardJson).card ?? resp.cardJson;
20376
+ } catch {
20377
+ card = resp.cardJson;
20378
+ }
20379
+ if (!card || card.trim().length === 0) return null;
20380
+ return "<bot_persona>\n" + card + "\n</bot_persona>";
20381
+ } catch {
20382
+ return null;
20383
+ }
20384
+ }
20284
20385
  async function runCompaction(args) {
20285
20386
  const request3 = buildCompactSessionRequest(args);
20286
20387
  try {
20287
20388
  const client = await runtime.getClient();
20288
20389
  const threshold = getDynamicCompactThreshold(args.tokenBudget);
20289
- return normalizeCompactResult(await client.compactSession(request3), {
20390
+ const result = normalizeCompactResult(await client.compactSession(request3), {
20290
20391
  tokensBefore: args.currentTokenCount,
20291
20392
  logger,
20292
20393
  ...threshold != null ? { threshold } : {}
20293
20394
  });
20395
+ if (result.ok && result.compacted) {
20396
+ activateCompactedProjection(args.sessionId, "compact");
20397
+ }
20398
+ return result;
20294
20399
  } catch (error2) {
20295
20400
  return {
20296
20401
  ok: false,
@@ -20347,6 +20452,7 @@ ${cached}
20347
20452
  const sessionId = requireSessionId(args.sessionId, "bootstrap");
20348
20453
  predictiveContextCache.delete(sessionId);
20349
20454
  postToolRecallCache.delete(sessionId);
20455
+ turnCache.invalidateSession(sessionId);
20350
20456
  asyncIngestionQueues.delete(sessionId);
20351
20457
  const userId = resolveUserId({
20352
20458
  userIdOverride: args.userId,
@@ -20390,13 +20496,26 @@ ${cached}
20390
20496
  },
20391
20497
  async assemble(args) {
20392
20498
  const sessionId = requireSessionId(args.sessionId, "assemble");
20499
+ let compactionProjectionActive = compactedProjectionSessions.has(sessionId);
20393
20500
  const userId = resolveUserId({
20394
20501
  userIdOverride: args.userId,
20395
20502
  sessionKey: args.sessionKey
20396
20503
  });
20397
20504
  const normalizeWindow = 50;
20398
- const recentMessages = args.messages.length > normalizeWindow ? args.messages.slice(-normalizeWindow) : args.messages;
20505
+ const recentMessages = selectTurnAlignedSourceSuffix(args.messages, normalizeWindow);
20399
20506
  const messages = normalizeKernelMessages(recentMessages);
20507
+ const projectedSourceMessages = () => compactionProjectionActive ? recentMessages : args.messages;
20508
+ const buildAssembleFallback = () => {
20509
+ if (!compactionProjectionActive) {
20510
+ return buildBudgetFallbackContext(args.messages, args.tokenBudget);
20511
+ }
20512
+ return enforceCompactedProjectionBudgetInvariant({
20513
+ messages: projectedSourceMessages(),
20514
+ estimatedTokens: approximateMessagesTokens(projectedSourceMessages()),
20515
+ systemPromptAddition: "",
20516
+ promptAuthority: "assembled"
20517
+ }, args.tokenBudget);
20518
+ };
20400
20519
  const strippedPrompt = args.prompt ? normalizeKernelContent(args.prompt, { retainOpenClawContext: false }) : "";
20401
20520
  const lastUserIndex = findLastUserMessageIndex(messages);
20402
20521
  const isPostToolContinuation = lastUserIndex >= 0 && lastUserIndex < messages.length - 1 && hasLiveToolProtocolAfterLastUser(messages, lastUserIndex);
@@ -20442,17 +20561,23 @@ ${cached}
20442
20561
  compacted: compactionResult.compacted,
20443
20562
  reason: compactionResult.reason
20444
20563
  });
20445
- if (!compactionResult.ok) {
20564
+ compactionProjectionActive = compactedProjectionSessions.has(sessionId);
20565
+ if (!compactionResult.ok && !compactionProjectionActive) {
20446
20566
  logger.info?.(
20447
20567
  `LibraVDB predictive compaction blocked assemble path at ${currentContextTokens} tokens (threshold=${dynamicCompactThreshold}): ${compactionResult.reason ?? "compaction failed"}`
20448
20568
  );
20449
20569
  return ensureReplaySafeUserTurn(
20450
- buildBudgetFallbackContext(args.messages, args.tokenBudget),
20570
+ buildAssembleFallback(),
20451
20571
  args.messages,
20452
20572
  logger,
20453
20573
  args.tokenBudget
20454
20574
  );
20455
20575
  }
20576
+ if (!compactionResult.ok) {
20577
+ logger.info?.(
20578
+ `LibraVDB predictive compaction made no additional progress at ${currentContextTokens} tokens; continuing with the existing daemon-owned compacted projection.`
20579
+ );
20580
+ }
20456
20581
  }
20457
20582
  const btLog = cfg.beforeTurnDebug ? (msg) => logger.info?.(msg) : (_msg) => {
20458
20583
  };
@@ -20499,10 +20624,20 @@ ${cached}
20499
20624
  }
20500
20625
  }
20501
20626
  if (cachedSystemPrompt !== void 0) {
20502
- const mockResp = { messages: args.messages, systemPromptAddition: cachedSystemPrompt };
20503
- enforced = enforceTokenBudgetInvariant(
20504
- normalizeAssembleResult(mockResp, args.messages),
20505
- args.tokenBudget
20627
+ if (hasCompactedSessionContext(cachedSystemPrompt)) {
20628
+ activateCompactedProjection(sessionId, "assemble");
20629
+ compactionProjectionActive = true;
20630
+ }
20631
+ const sourceProjection = projectedSourceMessages();
20632
+ const mockResp = { messages: sourceProjection, systemPromptAddition: cachedSystemPrompt };
20633
+ enforced = enforceAssembleBudget(
20634
+ normalizeAssembleResult(
20635
+ mockResp,
20636
+ sourceProjection,
20637
+ compactionProjectionActive ? "assembled" : PROMPT_AUTHORITY_PREASSEMBLY_MAY_OVERFLOW
20638
+ ),
20639
+ args.tokenBudget,
20640
+ compactionProjectionActive
20506
20641
  );
20507
20642
  } else {
20508
20643
  const pending = asyncIngestionQueues.get(sessionId);
@@ -20564,7 +20699,16 @@ ${cached}
20564
20699
  (_, reject) => setTimeout(() => reject(new Error(`AssembleContextInternal timed out after ${assembleTimeout}ms`)), assembleTimeout)
20565
20700
  )
20566
20701
  ]);
20567
- const assembled = normalizeAssembleResult(resp, args.messages);
20702
+ if (typeof resp.systemPromptAddition === "string" && hasCompactedSessionContext(resp.systemPromptAddition)) {
20703
+ activateCompactedProjection(sessionId, "assemble");
20704
+ compactionProjectionActive = true;
20705
+ }
20706
+ const sourceProjection = projectedSourceMessages();
20707
+ const assembled = normalizeAssembleResult(
20708
+ resp,
20709
+ sourceProjection,
20710
+ compactionProjectionActive ? "assembled" : PROMPT_AUTHORITY_PREASSEMBLY_MAY_OVERFLOW
20711
+ );
20568
20712
  const continuityContext = await injectContinuityContext({
20569
20713
  client,
20570
20714
  userId,
@@ -20575,10 +20719,14 @@ ${cached}
20575
20719
  systemPromptAddition: assembled.systemPromptAddition
20576
20720
  });
20577
20721
  const userCardContext = await injectUserCardContext({ client, userId });
20722
+ const personaContext = await injectPersonaContext({ client });
20578
20723
  const rulesContext = buildRulesContext();
20579
20724
  const isSessionBootstrap = messages.length <= 1;
20580
20725
  let withContext = assembled;
20581
20726
  if (isSessionBootstrap) {
20727
+ if (personaContext) {
20728
+ withContext = { ...withContext, systemPromptAddition: appendSystemPromptAddition(withContext.systemPromptAddition, personaContext) };
20729
+ }
20582
20730
  if (userCardContext) {
20583
20731
  withContext = { ...withContext, systemPromptAddition: appendSystemPromptAddition(withContext.systemPromptAddition, userCardContext) };
20584
20732
  }
@@ -20589,7 +20737,7 @@ ${cached}
20589
20737
  withContext = { ...withContext, systemPromptAddition: appendSystemPromptAddition(withContext.systemPromptAddition, rulesContext) };
20590
20738
  }
20591
20739
  }
20592
- enforced = enforceTokenBudgetInvariant(
20740
+ enforced = enforceAssembleBudget(
20593
20741
  await augmentWithExactRecall(withContext, {
20594
20742
  queryText: retrievalQuery,
20595
20743
  userId,
@@ -20597,7 +20745,8 @@ ${cached}
20597
20745
  tokenBudget: args.tokenBudget,
20598
20746
  reservedTokens: reservedCurrentTurnTokens
20599
20747
  }),
20600
- args.tokenBudget
20748
+ args.tokenBudget,
20749
+ compactionProjectionActive
20601
20750
  );
20602
20751
  const predictions = predictiveContextCache.get(sessionId) || [];
20603
20752
  predictiveContextCache.delete(sessionId);
@@ -20654,9 +20803,10 @@ ${cached}
20654
20803
  systemPromptAddition: enforced.systemPromptAddition
20655
20804
  });
20656
20805
  }
20657
- enforced = enforceTokenBudgetInvariant(
20806
+ enforced = enforceAssembleBudget(
20658
20807
  enforced,
20659
- args.tokenBudget
20808
+ args.tokenBudget,
20809
+ compactionProjectionActive
20660
20810
  );
20661
20811
  return ensureReplaySafeUserTurn(enforced, args.messages, logger, args.tokenBudget);
20662
20812
  } catch (error2) {
@@ -20664,7 +20814,7 @@ ${cached}
20664
20814
  `LibraVDB assemble failed, using budget-clamped fallback context: ${error2 instanceof Error ? error2.message : String(error2)}`
20665
20815
  );
20666
20816
  return ensureReplaySafeUserTurn(
20667
- buildBudgetFallbackContext(args.messages, args.tokenBudget),
20817
+ buildAssembleFallback(),
20668
20818
  args.messages,
20669
20819
  logger,
20670
20820
  args.tokenBudget
@@ -20852,6 +21002,7 @@ ${cached}
20852
21002
  }
20853
21003
  }
20854
21004
  predictiveContextCache.clear();
21005
+ compactedProjectionSessions.clear();
20855
21006
  postToolRecallCache.clear();
20856
21007
  asyncIngestionQueues.clear();
20857
21008
  triggerCache.clear();
@@ -31174,6 +31325,12 @@ function buildToolGuidance(availableTools) {
31174
31325
  return [];
31175
31326
  }
31176
31327
  const lines = [];
31328
+ const hasSearch = availableTools.has("memory_search");
31329
+ const hasGet = availableTools.has("memory_get");
31330
+ const hasDescribe = availableTools.has("memory_describe");
31331
+ const hasExpand = availableTools.has("memory_expand");
31332
+ const hasGrep = availableTools.has("memory_grep");
31333
+ const hasUserCard = availableTools.has("get_user_card");
31177
31334
  const hasGetCard = availableTools.has("get_user_card");
31178
31335
  const hasListCards = availableTools.has("list_user_cards");
31179
31336
  if (hasGetCard || hasListCards) {
@@ -31184,7 +31341,7 @@ function buildToolGuidance(availableTools) {
31184
31341
  hasListCards ? "- `list_user_cards()` \u2014 MANDATORY roster check. Call if unsure whether a card exists." : "",
31185
31342
  "Cards are the canonical record. You MUST call these tools. Do NOT answer from",
31186
31343
  "memory, context, or training data without checking the card first.",
31187
- "Only use memory_search if the card is empty or missing.",
31344
+ hasSearch ? "Only use memory_search if the card is empty or missing." : "Use the card result as the available identity record.",
31188
31345
  "",
31189
31346
  "**Autonomous card maintenance:**",
31190
31347
  hasGetCard ? "- When ANY speaker is mentioned with new or changed information (status, relationships, jobs, life events, feelings), call `update_user_card` BEFORE responding. Update the card first, then reply. Do NOT wait to be asked. Build the world picture proactively. Every person the user mentions matters." : "",
@@ -31192,20 +31349,19 @@ function buildToolGuidance(availableTools) {
31192
31349
  ""
31193
31350
  );
31194
31351
  }
31195
- lines.push(
31196
- "Call `memory_search` once per user question for prior turns, remembered",
31197
- "facts, earliest interactions, and channel history. Do not answer memory",
31198
- "questions from prior transcript claims \u2014 perform a search every time.",
31199
- "After receiving results, use them directly; do not re-call in the same turn.",
31200
- ...availableTools.has("memory_get") ? [
31201
- "After a `memory_search` hit, call `memory_get` when exact wording or more context is needed.",
31202
- "IMPORTANT: If a search snippet is cluttered with metadata, do NOT claim nothing was found. Call `memory_get` on the hit's path to read the full record first. The data is there \u2014 expand before giving up."
31203
- ] : [],
31204
- ""
31205
- );
31206
- const hasDescribe = availableTools.has("memory_describe");
31207
- const hasExpand = availableTools.has("memory_expand");
31208
- const hasGrep = availableTools.has("memory_grep");
31352
+ if (hasSearch) {
31353
+ lines.push(
31354
+ "Call `memory_search` once per user question for prior turns, remembered",
31355
+ "facts, earliest interactions, and channel history. Do not answer memory",
31356
+ "questions from prior transcript claims \u2014 perform a search every time.",
31357
+ "After receiving results, use them directly; do not re-call in the same turn.",
31358
+ ...hasGet ? [
31359
+ "After a `memory_search` hit, call `memory_get` when exact wording or more context is needed.",
31360
+ "IMPORTANT: If a search snippet is cluttered with metadata, do NOT claim nothing was found. Call `memory_get` on the hit's path to read the full record first. The data is there \u2014 expand before giving up."
31361
+ ] : [],
31362
+ ""
31363
+ );
31364
+ }
31209
31365
  if (hasDescribe || hasExpand || hasGrep) {
31210
31366
  lines.push(
31211
31367
  "**Compacted summaries \u2014 recall hierarchy (cheap \u2192 expensive):**",
@@ -31252,13 +31408,16 @@ function buildToolGuidance(availableTools) {
31252
31408
  ""
31253
31409
  );
31254
31410
  if (hasExpand) {
31411
+ const traversalSteps = [
31412
+ hasSearch ? "1. `memory_search` for the people/events in question to get record IDs" : "1. Start from a record ID already present in context or another tool result",
31413
+ "2. `memory_expand` with the most relevant `record_id` to walk typed causal and 6W1H hop edges",
31414
+ hasGet ? "3. Follow interesting edges; use `memory_get` for exact detail on connected records" : "3. Use the typed edge snippets to decide whether the connected records answer the question",
31415
+ ...hasUserCard ? ["4. Use `get_user_card` to cross-reference identity context"] : []
31416
+ ];
31255
31417
  lines.push(
31256
31418
  "### Causal Graph Traversal",
31257
31419
  "When the user asks about causes, patterns, or relationships:",
31258
- "1. `memory_search` for the people/events in question to get record IDs",
31259
- "2. `memory_expand` with the most relevant `record_id` to walk causal edges",
31260
- "3. Follow interesting edges \u2014 use `memory_get` for full detail on connected records",
31261
- "4. Use `get_user_card` to cross-reference identity context",
31420
+ ...traversalSteps,
31262
31421
  ""
31263
31422
  );
31264
31423
  }
@@ -31446,7 +31605,7 @@ function createMemoryExpandTool(getClient, getSessionKey, logger = console, getS
31446
31605
  return {
31447
31606
  name: "memory_expand",
31448
31607
  label: "Memory Expand",
31449
- description: "Expand compacted summaries OR walk causal graph edges from ANY record. Summary mode (summaryIds): walk the summary tree up to maxDepth levels. Graph mode (record_id): walk causal edges (why_ids/how_ids/hop_targets) from a record ID. Use exact IDs from memory_search or memory_get results \u2014 any ingested turn, memory, or summary has graph edges. After get_user_card for context, search for related people/events then expand the most relevant hit. For large expansions, spawns a sub-agent. Use memory_describe first to check if expansion is warranted.",
31608
+ description: "Expand compacted summaries OR walk causal graph edges from ANY record. Summary mode (summaryIds): walk the summary tree up to maxDepth levels. Graph mode (record_id): walk causal edges (why_ids/how_ids/hop_targets) from a record ID. Use exact IDs from memory_search or memory_get results \u2014 any ingested turn, memory, or summary has graph edges. For large expansions, spawns a sub-agent. Use memory_describe first to check if expansion is warranted.",
31450
31609
  parameters: MEMORY_EXPAND_SCHEMA,
31451
31610
  execute: async (_toolCallId, rawParams) => {
31452
31611
  const params = asParams(rawParams);
@@ -31461,10 +31620,16 @@ function createMemoryExpandTool(getClient, getSessionKey, logger = console, getS
31461
31620
  const client = await getClient();
31462
31621
  const resp = await client.expandSummary({ recordId, maxDepth });
31463
31622
  let text = resp.text ?? "";
31464
- const connected = resp.connected;
31465
- if (connected && connected.length > 0) {
31623
+ const connected = (resp.connected ?? []).map((c) => ({
31624
+ recordId: c.recordId,
31625
+ text: c.text ?? "",
31626
+ depth: c.depth,
31627
+ edgeType: c.edgeType || "unknown",
31628
+ edgeWeight: c.edgeWeight ?? 0
31629
+ }));
31630
+ if (connected.length > 0) {
31466
31631
  text = connected.map(
31467
- (c) => `[depth=${c.depth}] ${c.recordId}: ${c.text || ""}`
31632
+ (c) => `[depth=${c.depth} edge=${c.edgeType} weight=${c.edgeWeight}] ${c.recordId}: ${c.text}`
31468
31633
  ).join("\n\n");
31469
31634
  }
31470
31635
  if (!text && resp.whyIds?.length) {
@@ -31474,7 +31639,7 @@ hop_targets: ${resp.hopTargets?.join(", ") ?? "none"}`;
31474
31639
  }
31475
31640
  return {
31476
31641
  content: [{ type: "text", text: text || "(no graph edges found)" }],
31477
- details: { summaryId: recordId, depth: maxDepth, text: text || "", truncated: false, exceededBudget: false, parentCount: connected?.length ?? 0 }
31642
+ details: { summaryId: recordId, depth: maxDepth, text: text || "", truncated: false, exceededBudget: false, parentCount: connected.length, connected }
31478
31643
  };
31479
31644
  } catch (error2) {
31480
31645
  logger.warn?.(`memory_expand graph mode failed: ${formatError(error2)}`);
@@ -31788,6 +31953,55 @@ function createListUserCardsTool(getClient, logger = console) {
31788
31953
  }
31789
31954
  };
31790
31955
  }
31956
+ function createSetPersonaTool(getClient, logger = console) {
31957
+ return {
31958
+ name: "set_persona",
31959
+ label: "Set Persona",
31960
+ description: "Define who YOU are \u2014 your personality, tone, boundaries, and behavior. Write in prose like you're describing yourself. This is injected as <bot_persona> at the start of every session. Update it when your persona changes. The LLM will embody this persona in all interactions.",
31961
+ parameters: {
31962
+ type: "object",
31963
+ additionalProperties: false,
31964
+ properties: {
31965
+ persona: { type: "string", description: "Prose description of how you should behave." }
31966
+ },
31967
+ required: ["persona"]
31968
+ },
31969
+ execute: async (_toolCallId, rawParams) => {
31970
+ const params = rawParams;
31971
+ const persona = typeof params?.persona === "string" ? params.persona.trim() : "";
31972
+ if (!persona) return jsonResult2({ ok: false, error: "set_persona requires a persona string" });
31973
+ try {
31974
+ const client = await getClient();
31975
+ const resp = await client.upsertUserCard({
31976
+ userId: "__bot_persona__",
31977
+ cardJson: JSON.stringify({ card: persona, updatedAt: Date.now() })
31978
+ });
31979
+ return jsonResult2({ ok: resp.ok });
31980
+ } catch (error2) {
31981
+ logger.warn?.(`set_persona failed: ${formatError(error2)}`);
31982
+ return jsonResult2({ ok: false, error: formatError(error2) });
31983
+ }
31984
+ }
31985
+ };
31986
+ }
31987
+ function createGetPersonaTool(getClient, logger = console) {
31988
+ return {
31989
+ name: "get_persona",
31990
+ label: "Get Persona",
31991
+ description: "Read your current persona. Returns the full prose description of how you should behave.",
31992
+ parameters: { type: "object", additionalProperties: false, properties: {} },
31993
+ execute: async () => {
31994
+ try {
31995
+ const client = await getClient();
31996
+ const resp = await client.getUserCard({ userId: "__bot_persona__" });
31997
+ return jsonResult2({ persona: resp.cardJson || null });
31998
+ } catch (error2) {
31999
+ logger.warn?.(`get_persona failed: ${formatError(error2)}`);
32000
+ return jsonResult2({ error: formatError(error2) });
32001
+ }
32002
+ }
32003
+ };
32004
+ }
31791
32005
 
31792
32006
  // src/memory-tools.ts
31793
32007
  var MEMORY_SEARCH_SCHEMA = {
@@ -35609,6 +35823,14 @@ function register(api) {
35609
35823
  api.registerTool?.(() => createGetRuleTool(logger), { names: ["get_rule"] });
35610
35824
  api.registerTool?.(() => createListRulesTool(logger), { names: ["list_rules"] });
35611
35825
  api.registerTool?.(() => createDeleteRuleTool(logger), { names: ["delete_rule"] });
35826
+ api.registerTool?.(() => {
35827
+ const getClient = runtimeOrNull.getClient;
35828
+ return createSetPersonaTool(getClient, logger);
35829
+ }, { names: ["set_persona"] });
35830
+ api.registerTool?.(() => {
35831
+ const getClient = runtimeOrNull.getClient;
35832
+ return createGetPersonaTool(getClient, logger);
35833
+ }, { names: ["get_persona"] });
35612
35834
  }
35613
35835
  if (isLightweight || isDiscovery) {
35614
35836
  if (!isLightweight) {
package/dist/manifest.js CHANGED
@@ -1,7 +1,7 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
3
- import * as crypto from "crypto";
4
- import * as os from "os";
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import * as crypto from "node:crypto";
4
+ import * as os from "node:os";
5
5
  export class TurnManifestStore {
6
6
  manifestDir;
7
7
  constructor() {
@@ -25,22 +25,27 @@ function buildToolGuidance(availableTools) {
25
25
  return [];
26
26
  }
27
27
  const lines = [];
28
+ const hasSearch = availableTools.has("memory_search");
29
+ const hasGet = availableTools.has("memory_get");
30
+ const hasDescribe = availableTools.has("memory_describe");
31
+ const hasExpand = availableTools.has("memory_expand");
32
+ const hasGrep = availableTools.has("memory_grep");
33
+ const hasUserCard = availableTools.has("get_user_card");
28
34
  // ── User card tools (identity-first override) ──
29
35
  const hasGetCard = availableTools.has("get_user_card");
30
36
  const hasListCards = availableTools.has("list_user_cards");
31
37
  if (hasGetCard || hasListCards) {
32
- lines.push("**Identity/Entity questions — MANDATORY card lookup:**", "BEFORE answering any question about a person, pet, place, or named thing:", hasGetCard ? "- `get_user_card(user_id)` — MANDATORY lookup for a specific entity." : "", hasListCards ? "- `list_user_cards()` — MANDATORY roster check. Call if unsure whether a card exists." : "", "Cards are the canonical record. You MUST call these tools. Do NOT answer from", "memory, context, or training data without checking the card first.", "Only use memory_search if the card is empty or missing.", "", "**Autonomous card maintenance:**", hasGetCard ? "- When ANY speaker is mentioned with new or changed information (status, relationships, jobs, life events, feelings), call `update_user_card` BEFORE responding. Update the card first, then reply. Do NOT wait to be asked. Build the world picture proactively. Every person the user mentions matters." : "", hasGetCard ? "- If a card for the speaker doesn't exist yet, CREATE one with `update_user_card`. Better to have a stub card than no card at all." : "", "");
38
+ lines.push("**Identity/Entity questions — MANDATORY card lookup:**", "BEFORE answering any question about a person, pet, place, or named thing:", hasGetCard ? "- `get_user_card(user_id)` — MANDATORY lookup for a specific entity." : "", hasListCards ? "- `list_user_cards()` — MANDATORY roster check. Call if unsure whether a card exists." : "", "Cards are the canonical record. You MUST call these tools. Do NOT answer from", "memory, context, or training data without checking the card first.", hasSearch ? "Only use memory_search if the card is empty or missing." : "Use the card result as the available identity record.", "", "**Autonomous card maintenance:**", hasGetCard ? "- When ANY speaker is mentioned with new or changed information (status, relationships, jobs, life events, feelings), call `update_user_card` BEFORE responding. Update the card first, then reply. Do NOT wait to be asked. Build the world picture proactively. Every person the user mentions matters." : "", hasGetCard ? "- If a card for the speaker doesn't exist yet, CREATE one with `update_user_card`. Better to have a stub card than no card at all." : "", "");
39
+ }
40
+ if (hasSearch) {
41
+ lines.push("Call `memory_search` once per user question for prior turns, remembered", "facts, earliest interactions, and channel history. Do not answer memory", "questions from prior transcript claims — perform a search every time.", "After receiving results, use them directly; do not re-call in the same turn.", ...(hasGet
42
+ ? [
43
+ "After a `memory_search` hit, call `memory_get` when exact wording or more context is needed.",
44
+ "IMPORTANT: If a search snippet is cluttered with metadata, do NOT claim nothing was found. Call `memory_get` on the hit's path to read the full record first. The data is there — expand before giving up."
45
+ ]
46
+ : []), "");
33
47
  }
34
- lines.push("Call `memory_search` once per user question for prior turns, remembered", "facts, earliest interactions, and channel history. Do not answer memory", "questions from prior transcript claims — perform a search every time.", "After receiving results, use them directly; do not re-call in the same turn.", ...(availableTools.has("memory_get")
35
- ? [
36
- "After a `memory_search` hit, call `memory_get` when exact wording or more context is needed.",
37
- "IMPORTANT: If a search snippet is cluttered with metadata, do NOT claim nothing was found. Call `memory_get` on the hit's path to read the full record first. The data is there — expand before giving up."
38
- ]
39
- : []), "");
40
48
  // ── Summaries / recall (when available) ──
41
- const hasDescribe = availableTools.has("memory_describe");
42
- const hasExpand = availableTools.has("memory_expand");
43
- const hasGrep = availableTools.has("memory_grep");
44
49
  if (hasDescribe || hasExpand || hasGrep) {
45
50
  lines.push("**Compacted summaries — recall hierarchy (cheap → expensive):**", "", "Summaries in search results show `[Summary sum_xxx]: [eviction cue]`.", "The cue lists what the summary covers — anchors (files, tools, versions),", "decisions, constraints, and signal counts. Many questions can be answered", "from the cue alone without expanding.", "");
46
51
  if (hasDescribe) {
@@ -58,7 +63,17 @@ function buildToolGuidance(availableTools) {
58
63
  lines.push("### Hard Constraint Rules", "Rules are injected at session start as `<hard_constraints>`. They are non-negotiable.", "Use `set_rule` to create one (max 20), `list_rules` to see current rules,", "`delete_rule` to remove one. Rules override all other instructions.", "Never reason around a rule, find loopholes, or deprioritize it.", "");
59
64
  // ── Causal graph traversal (when expand supports record_id) ──
60
65
  if (hasExpand) {
61
- lines.push("### Causal Graph Traversal", "When the user asks about causes, patterns, or relationships:", "1. `memory_search` for the people/events in question to get record IDs", "2. `memory_expand` with the most relevant `record_id` to walk causal edges", "3. Follow interesting edges — use `memory_get` for full detail on connected records", "4. Use `get_user_card` to cross-reference identity context", "");
66
+ const traversalSteps = [
67
+ hasSearch
68
+ ? "1. `memory_search` for the people/events in question to get record IDs"
69
+ : "1. Start from a record ID already present in context or another tool result",
70
+ "2. `memory_expand` with the most relevant `record_id` to walk typed causal and 6W1H hop edges",
71
+ hasGet
72
+ ? "3. Follow interesting edges; use `memory_get` for exact detail on connected records"
73
+ : "3. Use the typed edge snippets to decide whether the connected records answer the question",
74
+ ...(hasUserCard ? ["4. Use `get_user_card` to cross-reference identity context"] : []),
75
+ ];
76
+ lines.push("### Causal Graph Traversal", "When the user asks about causes, patterns, or relationships:", ...traversalSteps, "");
62
77
  }
63
78
  lines.push("LibraVDB memory is vector-backed and retrieved through tools, not files.", "");
64
79
  return lines;