@remnic/plugin-openclaw 9.25.6 → 9.26.0

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
@@ -5272,8 +5272,10 @@ function resolveRemnicOpenClawPluginEntry(raw, preferredId) {
5272
5272
  }
5273
5273
 
5274
5274
  // src/delegate-runtime.ts
5275
- import { log as log2 } from "@remnic/core/logger";
5276
5275
  import path4 from "path";
5276
+ import { renderMemoryContextPrompt } from "@remnic/core";
5277
+ import { log as log2 } from "@remnic/core/logger";
5278
+ import { SESSION_NAMESPACE_BINDING_MAX_ENTRIES, SESSION_NAMESPACE_BINDING_MAX_NAMESPACES, SESSION_NAMESPACE_BINDING_MAX_NAMESPACE_LENGTH, createFileSessionNamespaceBindingStore } from "@remnic/core/session-namespace-bindings";
5277
5279
  import { createFileToggleStore } from "@remnic/core/session-toggles";
5278
5280
 
5279
5281
  // src/bridge.ts
@@ -5604,7 +5606,7 @@ function extractTextContent(msg) {
5604
5606
  }
5605
5607
 
5606
5608
  // src/delegate-runtime.ts
5607
- var MEMORY_CONTEXT_HEADER = "## Memory Context (Remnic)";
5609
+ var DELEGATE_BATCH_FLUSH_CACHE_TTL_MS = 3e4;
5608
5610
  var DEFAULT_DELEGATE_AUTHORIZATION_OPERATIONS = [
5609
5611
  "recall",
5610
5612
  "observe",
@@ -5668,6 +5670,30 @@ async function postJson(target, serviceId, pathname, body, timeoutMs) {
5668
5670
  const parsed = await res.json().catch(() => null);
5669
5671
  return typeof parsed === "object" && parsed !== null ? parsed : null;
5670
5672
  }
5673
+ async function getJson(target, serviceId, pathname, timeoutMs) {
5674
+ const headers = {};
5675
+ const auth = target.resolveAuthToken();
5676
+ if (auth.token) headers.Authorization = `Bearer ${auth.token}`;
5677
+ const res = await fetch(daemonUrl(target, pathname), {
5678
+ headers,
5679
+ signal: AbortSignal.timeout(timeoutMs)
5680
+ });
5681
+ if (!res.ok) {
5682
+ if (res.status === 401 || res.status === 403) {
5683
+ const status = res.status === 401 ? 401 : 403;
5684
+ await res.body?.cancel();
5685
+ reportDaemonAuthorizationFailure(serviceId, pathname, status, auth.source);
5686
+ return { status: res.status, body: null };
5687
+ }
5688
+ await res.body?.cancel();
5689
+ return { status: res.status, body: null };
5690
+ }
5691
+ const parsed = await res.json().catch(() => null);
5692
+ return {
5693
+ status: res.status,
5694
+ body: typeof parsed === "object" && parsed !== null ? parsed : null
5695
+ };
5696
+ }
5671
5697
  function sessionKeyFrom(event, ctx) {
5672
5698
  const fromCtx = ctx?.sessionKey;
5673
5699
  if (typeof fromCtx === "string" && fromCtx.length > 0) return fromCtx;
@@ -5675,6 +5701,13 @@ function sessionKeyFrom(event, ctx) {
5675
5701
  if (typeof fromEvent === "string" && fromEvent.length > 0) return fromEvent;
5676
5702
  return "default";
5677
5703
  }
5704
+ function lifecycleSessionKeyFrom(event, ctx) {
5705
+ const fromEvent = event?.sessionKey;
5706
+ if (fromEvent !== void 0) {
5707
+ return typeof fromEvent === "string" && fromEvent.length > 0 ? fromEvent : void 0;
5708
+ }
5709
+ return sessionKeyFrom(event, ctx);
5710
+ }
5678
5711
  function recallQueryFrom(event) {
5679
5712
  let prompt = typeof event.prompt === "string" ? event.prompt : void 0;
5680
5713
  if ((!prompt || prompt.length < 5) && Array.isArray(event.messages)) {
@@ -5699,10 +5732,81 @@ function cwdFrom(event, ctx, fallback) {
5699
5732
  return fallback;
5700
5733
  }
5701
5734
  function withNamespace(namespace, body) {
5702
- return namespace.length > 0 ? { ...body, namespace } : body;
5735
+ return namespace ? { ...body, namespace } : body;
5736
+ }
5737
+ function explicitSessionNamespaceFrom(sessionKey, event, ctx) {
5738
+ const eventSessionKey = typeof event.sessionKey === "string" ? event.sessionKey : void 0;
5739
+ const ctxSessionKey = typeof ctx.sessionKey === "string" ? ctx.sessionKey : void 0;
5740
+ const sources = eventSessionKey === sessionKey ? [event, ctx] : ctxSessionKey === sessionKey ? [ctx, event] : [ctx, event];
5741
+ for (const source of sources) {
5742
+ const sourceSessionKey = typeof source.sessionKey === "string" ? source.sessionKey : void 0;
5743
+ if (sourceSessionKey !== sessionKey) continue;
5744
+ const runtime = source.runtime;
5745
+ if (typeof runtime !== "object" || runtime === null) continue;
5746
+ const agent = runtime.agent;
5747
+ if (typeof agent !== "object" || agent === null) continue;
5748
+ const session = agent.session;
5749
+ if (typeof session !== "object" || session === null) continue;
5750
+ const namespace = session.namespace;
5751
+ if (namespace !== void 0 && typeof namespace !== "string") {
5752
+ throw new Error("delegate session namespace metadata must be a string");
5753
+ }
5754
+ return { namespace: typeof namespace === "string" ? namespace.trim() || void 0 : void 0 };
5755
+ }
5756
+ return void 0;
5757
+ }
5758
+ async function rememberedNamespacesFor(sessionKey, namespaceBindings) {
5759
+ return namespaceBindings.namespacesFor(sessionKey);
5760
+ }
5761
+ async function rememberNamespace(sessionKey, namespace, namespaceBindings) {
5762
+ if (namespace.length > SESSION_NAMESPACE_BINDING_MAX_NAMESPACE_LENGTH) {
5763
+ throw new Error(
5764
+ `delegate session namespace exceeds the daemon limit of ${SESSION_NAMESPACE_BINDING_MAX_NAMESPACE_LENGTH} characters`
5765
+ );
5766
+ }
5767
+ try {
5768
+ await namespaceBindings.remember(sessionKey, namespace);
5769
+ } catch (err) {
5770
+ log2.warn(`delegate namespace binding persistence failed: ${String(err)}`);
5771
+ throw err;
5772
+ }
5773
+ }
5774
+ async function sessionNamespaceFrom(sessionKey, event, ctx, fallback, namespaceBindings) {
5775
+ const explicit = explicitSessionNamespaceFrom(sessionKey, event, ctx);
5776
+ if (explicit !== void 0) {
5777
+ await rememberNamespace(sessionKey, explicit.namespace ?? "", namespaceBindings);
5778
+ return explicit.namespace;
5779
+ }
5780
+ const remembered = await rememberedNamespacesFor(sessionKey, namespaceBindings);
5781
+ return remembered.length > 0 ? remembered.at(-1) || void 0 : fallback.trim() || void 0;
5782
+ }
5783
+ async function lifecycleSessionNamespacesFrom(sessionKey, event, ctx, fallback, namespaceBindings) {
5784
+ const explicit = explicitSessionNamespaceFrom(sessionKey, event, ctx);
5785
+ if (explicit !== void 0) {
5786
+ await rememberNamespace(sessionKey, explicit.namespace ?? "", namespaceBindings);
5787
+ }
5788
+ const remembered = await rememberedNamespacesFor(sessionKey, namespaceBindings);
5789
+ if (explicit !== void 0) {
5790
+ const explicitNamespace = explicit.namespace ?? "";
5791
+ const namespaces = remembered.includes(explicitNamespace) ? remembered : [...remembered, explicitNamespace];
5792
+ return namespaces.map((namespace) => namespace || void 0);
5793
+ }
5794
+ if (remembered.length > 0) return remembered.map((namespace) => namespace || void 0);
5795
+ return [fallback.trim() || void 0];
5796
+ }
5797
+ function readContextComposition(response, fallbackContext) {
5798
+ const candidate = response.contextComposition;
5799
+ if (typeof candidate !== "object" || candidate === null || !("context" in candidate) || typeof candidate.context !== "string") {
5800
+ return { context: fallbackContext };
5801
+ }
5802
+ if ("footer" in candidate && typeof candidate.footer === "string") {
5803
+ return { context: candidate.context, footer: candidate.footer };
5804
+ }
5805
+ return { context: candidate.context };
5703
5806
  }
5704
5807
  function registerDelegateRuntime(api, options) {
5705
- const { target, namespace } = options;
5808
+ const { target, namespace, namespaceBindings } = options;
5809
+ const now = options.now ?? Date.now;
5706
5810
  if (options.passive) {
5707
5811
  log2.info(
5708
5812
  `[${options.serviceId}] bridge mode delegate: memory slot not owned \u2014 passive, no hooks registered`
@@ -5729,11 +5833,18 @@ function registerDelegateRuntime(api, options) {
5729
5833
  return void 0;
5730
5834
  }
5731
5835
  const cwd = cwdFrom(event, ctx, options.cwd);
5836
+ const scopedNamespace = await sessionNamespaceFrom(
5837
+ sessionKey,
5838
+ event,
5839
+ ctx,
5840
+ namespace,
5841
+ namespaceBindings
5842
+ );
5732
5843
  const response = await postJson(
5733
5844
  target,
5734
5845
  options.serviceId,
5735
5846
  "/engram/v1/recall",
5736
- withNamespace(namespace, {
5847
+ withNamespace(scopedNamespace, {
5737
5848
  query,
5738
5849
  sessionKey,
5739
5850
  mode: "auto",
@@ -5746,12 +5857,14 @@ function registerDelegateRuntime(api, options) {
5746
5857
  if (typeof rawContext !== "string" || rawContext.trim().length === 0) {
5747
5858
  return void 0;
5748
5859
  }
5749
- const context = rawContext.length > options.recallBudgetChars ? rawContext.slice(0, options.recallBudgetChars) + "\n\n...(memory context trimmed)" : rawContext;
5750
- const prompt = `${MEMORY_CONTEXT_HEADER}
5751
-
5752
- ${context}`;
5860
+ const rendered = renderMemoryContextPrompt({
5861
+ ...readContextComposition(response ?? {}, rawContext),
5862
+ maxChars: options.recallBudgetChars
5863
+ });
5864
+ if (!rendered) return void 0;
5865
+ const prompt = rendered.prompt;
5753
5866
  if (useSectionBuilder) {
5754
- promptLinesBySession.set(sessionKey, prompt.split("\n"));
5867
+ promptLinesBySession.set(sessionKey, rendered.lines);
5755
5868
  return void 0;
5756
5869
  }
5757
5870
  return hook === "before_prompt_build" ? { prependSystemContext: prompt } : { prependSystemContext: prompt, prependContext: prompt };
@@ -5803,11 +5916,18 @@ ${context}`;
5803
5916
  if (turn.length === 0) return;
5804
5917
  try {
5805
5918
  const cwd = cwdFrom(event, ctx, options.cwd);
5919
+ const scopedNamespace = await sessionNamespaceFrom(
5920
+ sessionKey,
5921
+ event,
5922
+ ctx,
5923
+ namespace,
5924
+ namespaceBindings
5925
+ );
5806
5926
  await postJson(
5807
5927
  target,
5808
5928
  options.serviceId,
5809
5929
  "/engram/v1/observe",
5810
- withNamespace(namespace, {
5930
+ withNamespace(scopedNamespace, {
5811
5931
  sessionKey,
5812
5932
  messages: turn,
5813
5933
  ...cwd ? { cwd } : {},
@@ -5819,25 +5939,129 @@ ${context}`;
5819
5939
  log2.warn(`delegate observe failed: ${String(err)}`);
5820
5940
  }
5821
5941
  });
5942
+ let cachedBatchFlushSupport;
5943
+ let cachedBatchFlushSupportExpiresAt = 0;
5944
+ let supportsBatchFlushPromise;
5945
+ const invalidateCachedBatchFlushSupport = () => {
5946
+ cachedBatchFlushSupport = void 0;
5947
+ cachedBatchFlushSupportExpiresAt = 0;
5948
+ };
5949
+ const cacheBatchFlushSupport = (supported) => {
5950
+ cachedBatchFlushSupport = supported;
5951
+ cachedBatchFlushSupportExpiresAt = now() + DELEGATE_BATCH_FLUSH_CACHE_TTL_MS;
5952
+ };
5953
+ const supportsBatchFlush = (timeoutMs) => {
5954
+ if (cachedBatchFlushSupport !== void 0) {
5955
+ if (now() < cachedBatchFlushSupportExpiresAt) {
5956
+ return Promise.resolve(cachedBatchFlushSupport);
5957
+ }
5958
+ invalidateCachedBatchFlushSupport();
5959
+ }
5960
+ if (supportsBatchFlushPromise !== void 0) return supportsBatchFlushPromise;
5961
+ const probe = getJson(
5962
+ target,
5963
+ options.serviceId,
5964
+ "/engram/v1/capabilities",
5965
+ timeoutMs
5966
+ ).then((response) => {
5967
+ const isSuccessfulResponse = response.status >= 200 && response.status < 300;
5968
+ if (isSuccessfulResponse && response.body !== null) {
5969
+ const batchFlushSupport = response.body.lcmCompactionFlushBatch;
5970
+ if (typeof batchFlushSupport === "boolean") {
5971
+ cacheBatchFlushSupport(batchFlushSupport);
5972
+ return batchFlushSupport;
5973
+ }
5974
+ return false;
5975
+ }
5976
+ if (response.status === 204 || response.status === 404 || response.status === 405 || response.status === 501) {
5977
+ cacheBatchFlushSupport(false);
5978
+ }
5979
+ return false;
5980
+ }).catch(() => false);
5981
+ supportsBatchFlushPromise = probe.finally(() => {
5982
+ supportsBatchFlushPromise = void 0;
5983
+ });
5984
+ return supportsBatchFlushPromise;
5985
+ };
5822
5986
  const flushHandler = async (event, ctx) => {
5823
- const fromEvent = event?.sessionKey;
5824
- const sessionKey = typeof fromEvent === "string" && fromEvent.length > 0 ? fromEvent : sessionKeyFrom(event, ctx);
5825
5987
  try {
5826
- await postJson(
5988
+ const deadline = Date.now() + options.flushTimeoutMs;
5989
+ const remainingTimeout = () => Math.max(1, deadline - Date.now());
5990
+ const sessionKey = lifecycleSessionKeyFrom(event, ctx);
5991
+ if (sessionKey === void 0) {
5992
+ log2.warn("delegate flush skipped: lifecycle event has malformed session key");
5993
+ return false;
5994
+ }
5995
+ const namespaces = await lifecycleSessionNamespacesFrom(
5996
+ sessionKey,
5997
+ event,
5998
+ ctx,
5999
+ namespace,
6000
+ namespaceBindings
6001
+ );
6002
+ const flushNamespace = (sessionNamespace) => postJson(
5827
6003
  target,
5828
6004
  options.serviceId,
5829
6005
  "/engram/v1/lcm/compaction/flush",
5830
- withNamespace(namespace, { sessionKey }),
5831
- options.flushTimeoutMs
6006
+ withNamespace(sessionNamespace, { sessionKey }),
6007
+ remainingTimeout()
5832
6008
  );
6009
+ const flushIndividually = async () => {
6010
+ const outcomes = await Promise.allSettled(namespaces.map(flushNamespace));
6011
+ return outcomes.every(
6012
+ (outcome) => outcome.status === "fulfilled" && outcome.value !== null && outcome.value.flushed === true
6013
+ );
6014
+ };
6015
+ if (namespaces.length <= 1 || await supportsBatchFlush(remainingTimeout())) {
6016
+ if (namespaces.length <= 1) {
6017
+ const response = await flushNamespace(namespaces[0]);
6018
+ return response !== null && response.flushed === true;
6019
+ }
6020
+ try {
6021
+ const response = await postJson(
6022
+ target,
6023
+ options.serviceId,
6024
+ "/engram/v1/lcm/compaction/flush",
6025
+ {
6026
+ sessionKey,
6027
+ namespaces: namespaces.map((sessionNamespace) => sessionNamespace ?? "")
6028
+ },
6029
+ remainingTimeout()
6030
+ );
6031
+ if (response === null) {
6032
+ invalidateCachedBatchFlushSupport();
6033
+ return flushIndividually();
6034
+ }
6035
+ const responseNamespaces = response.namespaces;
6036
+ const responseResults = response.results;
6037
+ const isBatchResponse = Array.isArray(responseNamespaces) && Array.isArray(responseResults) && responseNamespaces.length === namespaces.length && responseNamespaces.every(
6038
+ (responseNamespace, index) => responseNamespace === (namespaces[index] ?? "")
6039
+ ) && responseResults.length === namespaces.length;
6040
+ if (!isBatchResponse) {
6041
+ invalidateCachedBatchFlushSupport();
6042
+ return flushIndividually();
6043
+ }
6044
+ if (response.flushed !== true) {
6045
+ invalidateCachedBatchFlushSupport();
6046
+ return false;
6047
+ }
6048
+ return true;
6049
+ } catch {
6050
+ invalidateCachedBatchFlushSupport();
6051
+ return flushIndividually();
6052
+ }
6053
+ }
6054
+ return flushIndividually();
5833
6055
  } catch (err) {
5834
6056
  log2.warn(`delegate flush failed: ${String(err)}`);
6057
+ return false;
5835
6058
  }
5836
6059
  };
5837
6060
  api.on("before_compaction", flushHandler);
5838
6061
  if (options.flushOnResetEnabled) {
5839
- api.on("before_reset", flushHandler);
5840
- api.on("session_end", flushHandler);
6062
+ const flushEndedSession = async (event, ctx) => flushHandler(event, ctx);
6063
+ api.on("before_reset", flushEndedSession);
6064
+ api.on("session_end", flushEndedSession);
5841
6065
  }
5842
6066
  log2.info(
5843
6067
  `[${options.serviceId}] bridge mode delegate: memory loop backed by daemon at ${target.host}:${target.port} (embedded orchestrator skipped; tools/CLI/surfaces stay daemon-side)`
@@ -5846,7 +6070,130 @@ ${context}`;
5846
6070
  function activeDelegateAuthorizationOperations(options) {
5847
6071
  return options.allowPromptInjection && options.recallBudgetChars !== 0 ? DEFAULT_DELEGATE_AUTHORIZATION_OPERATIONS : DEFAULT_DELEGATE_AUTHORIZATION_OPERATIONS.slice(1);
5848
6072
  }
6073
+ var delegateNamespaceMigrationChains = /* @__PURE__ */ new Map();
6074
+ var queueDelegateNamespaceMigration = (bindingPath, sessionKey, operation) => {
6075
+ let sessionChains = delegateNamespaceMigrationChains.get(bindingPath);
6076
+ if (sessionChains === void 0) {
6077
+ sessionChains = /* @__PURE__ */ new Map();
6078
+ delegateNamespaceMigrationChains.set(bindingPath, sessionChains);
6079
+ }
6080
+ const prior = sessionChains.get(sessionKey) ?? Promise.resolve();
6081
+ const run = prior.catch(() => void 0).then(operation);
6082
+ const settled = run.then(
6083
+ () => void 0,
6084
+ () => void 0
6085
+ );
6086
+ sessionChains.set(sessionKey, settled);
6087
+ void settled.then(() => {
6088
+ if (sessionChains?.get(sessionKey) !== settled) return;
6089
+ sessionChains.delete(sessionKey);
6090
+ if (sessionChains.size === 0 && delegateNamespaceMigrationChains.get(bindingPath) === sessionChains) {
6091
+ delegateNamespaceMigrationChains.delete(bindingPath);
6092
+ }
6093
+ });
6094
+ return run;
6095
+ };
6096
+ function createDelegateNamespaceBindingStore(memoryDir, serviceId, isLegacyAdapterActive) {
6097
+ const bindingPath = (pluginId) => path4.join(memoryDir, "state", "plugins", pluginId, "session-namespace-bindings.json");
6098
+ const primaryPath = bindingPath(serviceId);
6099
+ const primary = createFileSessionNamespaceBindingStore(primaryPath);
6100
+ if (serviceId !== REMNIC_OPENCLAW_PLUGIN_ID) return primary;
6101
+ const legacy = createFileSessionNamespaceBindingStore(
6102
+ bindingPath(REMNIC_OPENCLAW_LEGACY_PLUGIN_ID)
6103
+ );
6104
+ const migratedLegacySessions = /* @__PURE__ */ new Set();
6105
+ const rememberMigratedLegacySession = (sessionKey) => {
6106
+ if (migratedLegacySessions.has(sessionKey)) return;
6107
+ migratedLegacySessions.add(sessionKey);
6108
+ while (migratedLegacySessions.size > SESSION_NAMESPACE_BINDING_MAX_ENTRIES) {
6109
+ const oldest = migratedLegacySessions.values().next().value;
6110
+ if (oldest === void 0) return;
6111
+ migratedLegacySessions.delete(oldest);
6112
+ }
6113
+ };
6114
+ const queueSessionMigration = (sessionKey, operation) => queueDelegateNamespaceMigration(primaryPath, sessionKey, operation);
6115
+ const readLegacyNamespaces = async (sessionKey, current) => {
6116
+ if (!isLegacyAdapterActive() && migratedLegacySessions.has(sessionKey)) return [];
6117
+ try {
6118
+ const previous = await legacy.namespacesFor(sessionKey);
6119
+ if (previous.length === 0) rememberMigratedLegacySession(sessionKey);
6120
+ return previous;
6121
+ } catch (err) {
6122
+ if (current.length > 0) {
6123
+ log2.warn(
6124
+ `[${serviceId}] delegate legacy namespace read failed; using canonical bindings: ${String(err)}`
6125
+ );
6126
+ return [];
6127
+ }
6128
+ throw err;
6129
+ }
6130
+ };
6131
+ const mergeNamespaceHistory = (current, previous) => {
6132
+ const merged = [];
6133
+ for (const remembered of [...previous, ...current]) {
6134
+ const existing = merged.indexOf(remembered);
6135
+ if (existing >= 0) merged.splice(existing, 1);
6136
+ merged.push(remembered);
6137
+ }
6138
+ return merged.slice(-SESSION_NAMESPACE_BINDING_MAX_NAMESPACES);
6139
+ };
6140
+ const persistNamespaceHistory = async (store, sessionKey, namespaces) => {
6141
+ if (store.replace !== void 0) {
6142
+ await store.replace(sessionKey, namespaces);
6143
+ return;
6144
+ }
6145
+ for (const namespace of namespaces) {
6146
+ await store.remember(sessionKey, namespace);
6147
+ }
6148
+ };
6149
+ const completeLegacyMigration = async (sessionKey) => {
6150
+ if (!isLegacyAdapterActive()) {
6151
+ try {
6152
+ await legacy.replace?.(sessionKey, []);
6153
+ } catch (err) {
6154
+ log2.warn(`[${serviceId}] delegate legacy namespace cleanup failed: ${String(err)}`);
6155
+ }
6156
+ }
6157
+ rememberMigratedLegacySession(sessionKey);
6158
+ };
6159
+ return {
6160
+ async namespacesFor(sessionKey) {
6161
+ return queueSessionMigration(sessionKey, async () => {
6162
+ const current = await primary.namespacesFor(sessionKey);
6163
+ const previous = await readLegacyNamespaces(sessionKey, current);
6164
+ if (previous.length === 0) return current;
6165
+ const merged = mergeNamespaceHistory(current, previous);
6166
+ const hasMissingLegacy = previous.some((remembered) => !current.includes(remembered));
6167
+ if (!hasMissingLegacy) {
6168
+ await completeLegacyMigration(sessionKey);
6169
+ return current;
6170
+ }
6171
+ try {
6172
+ await persistNamespaceHistory(primary, sessionKey, merged);
6173
+ await completeLegacyMigration(sessionKey);
6174
+ } catch (err) {
6175
+ log2.warn(`[${serviceId}] delegate namespace migration failed: ${String(err)}`);
6176
+ }
6177
+ return merged;
6178
+ });
6179
+ },
6180
+ async remember(sessionKey, namespace) {
6181
+ return queueSessionMigration(sessionKey, async () => {
6182
+ const current = await primary.namespacesFor(sessionKey);
6183
+ const previous = await readLegacyNamespaces(sessionKey, current);
6184
+ if (previous.length === 0) {
6185
+ await primary.remember(sessionKey, namespace);
6186
+ return;
6187
+ }
6188
+ const merged = mergeNamespaceHistory([...current, namespace], previous);
6189
+ await persistNamespaceHistory(primary, sessionKey, merged);
6190
+ await completeLegacyMigration(sessionKey);
6191
+ });
6192
+ }
6193
+ };
6194
+ }
5849
6195
  var delegateHookApiServices = /* @__PURE__ */ new WeakMap();
6196
+ var delegateActiveServiceIds = /* @__PURE__ */ new Set();
5850
6197
  var delegateEmbeddedFallbackApis = /* @__PURE__ */ new WeakSet();
5851
6198
  var delegateAuthorizationPreflightServices = /* @__PURE__ */ new WeakMap();
5852
6199
  function maybeRegisterDelegateRuntime(api, options, deps = { checkHealth: checkDaemonHealthSync }) {
@@ -5886,6 +6233,7 @@ function maybeRegisterDelegateRuntime(api, options, deps = { checkHealth: checkD
5886
6233
  (boundServices ?? delegateHookApiServices.set(api, /* @__PURE__ */ new Set()).get(api))?.add(
5887
6234
  options.serviceId
5888
6235
  );
6236
+ delegateActiveServiceIds.add(options.serviceId);
5889
6237
  }
5890
6238
  const toggleStore = options.sessionTogglesEnabled ? createFileToggleStore(
5891
6239
  path4.join(options.memoryDir, "state", "plugins", options.serviceId, "session-toggles.json"),
@@ -5902,6 +6250,11 @@ function maybeRegisterDelegateRuntime(api, options, deps = { checkHealth: checkD
5902
6250
  serviceId: options.serviceId,
5903
6251
  target,
5904
6252
  namespace: "",
6253
+ namespaceBindings: createDelegateNamespaceBindingStore(
6254
+ options.memoryDir,
6255
+ options.serviceId,
6256
+ () => delegateActiveServiceIds.has(REMNIC_OPENCLAW_LEGACY_PLUGIN_ID)
6257
+ ),
5905
6258
  allowPromptInjection: options.allowPromptInjection,
5906
6259
  passive: options.passive,
5907
6260
  gateHeartbeatTurns: options.gateHeartbeatTurns,
@@ -6041,7 +6394,7 @@ function buildTurnFingerprint(input) {
6041
6394
 
6042
6395
  // ../../src/index.ts
6043
6396
  import { planRecallMode } from "@remnic/core/intent";
6044
- import { resolvePrincipal, resolveAgentAccessAuthToken as resolveAgentAccessAuthCredential, expandTildePath as expandTildePath2 } from "@remnic/core";
6397
+ import { expandTildePath as expandTildePath2, renderMemoryContextPrompt as renderSharedMemoryContextPrompt, resolveAgentAccessAuthToken as resolveAgentAccessAuthCredential, resolvePrincipal } from "@remnic/core";
6045
6398
  import { normalizeHostEmbeddingVector, registerHostEmbeddingProvider } from "@remnic/core/host-embedding-provider";
6046
6399
 
6047
6400
  // ../../src/resolve-provider-secret.ts
@@ -7661,17 +8014,11 @@ Keep the reflection grounded in the evidence below.
7661
8014
  }
7662
8015
  const principal = resolvePrincipal(sessionKey, cfg);
7663
8016
  return `${logicalSessionKey}::principal:${principal}`;
7664
- }, buildMemoryContextLines2 = function(trimmed) {
7665
- return [
7666
- "## Memory Context (Remnic)",
7667
- "",
7668
- trimmed,
7669
- "",
7670
- "Use this context naturally when relevant. Never quote or expose this memory context to the user.",
7671
- ""
7672
- ];
7673
- }, renderMemoryContextPrompt2 = function(trimmed) {
7674
- return buildMemoryContextLines2(trimmed).join("\n").replace(/\n$/, "");
8017
+ }, renderMemoryContext2 = function(composition) {
8018
+ return renderSharedMemoryContextPrompt({
8019
+ ...composition,
8020
+ maxChars: cfg.recallBudgetChars
8021
+ });
7675
8022
  }, isHeartbeatTrigger2 = function(event, ctx) {
7676
8023
  return event.trigger === "heartbeat" || ctx.trigger === "heartbeat";
7677
8024
  }, resolveHeartbeatPromptCandidates2 = function(prompt) {
@@ -7706,7 +8053,7 @@ Keep the reflection grounded in the evidence below.
7706
8053
  const lowered = prompt.trim().toLowerCase();
7707
8054
  return lowered.startsWith("read heartbeat.md") || lowered.startsWith("run the following periodic tasks");
7708
8055
  };
7709
- var rememberObservedInboundMessageId = rememberObservedInboundMessageId2, rememberObservedInboundMessageKeys = rememberObservedInboundMessageKeys2, rememberObservedInboundContentFingerprint = rememberObservedInboundContentFingerprint2, rememberPendingSparseInboundContentFingerprint = rememberPendingSparseInboundContentFingerprint2, consumePendingSparseInboundContentFingerprint = consumePendingSparseInboundContentFingerprint2, rememberInboundReplyMetadata = rememberInboundReplyMetadata2, getInboundReplyMetadata = getInboundReplyMetadata2, rememberObservedInlineExplicitCaptureKey = rememberObservedInlineExplicitCaptureKey2, buildInlineExplicitCaptureDedupeKey = buildInlineExplicitCaptureDedupeKey2, resolveStoredCodexThreadId = resolveStoredCodexThreadId2, resolveStoredCodexBufferKey = resolveStoredCodexBufferKey2, addSessionToCodexIndex = addSessionToCodexIndex2, removeSessionFromCodexIndex = removeSessionFromCodexIndex2, resolveCodexCompactionBaselineKey = resolveCodexCompactionBaselineKey2, resolveCodexPromptCacheKey = resolveCodexPromptCacheKey2, rememberCodexThread = rememberCodexThread2, forgetCodexThread = forgetCodexThread2, clearCodexCompatCaches = clearCodexCompatCaches2, hasExplicitProviderIdentity = hasExplicitProviderIdentity2, cachePromptMemoryLines = cachePromptMemoryLines2, consumePromptMemoryLines = consumePromptMemoryLines2, resolveSessionIdentity = resolveSessionIdentity2, hasBufferedTurns = hasBufferedTurns2, resolveExtractionBufferKey = resolveExtractionBufferKey2, buildMemoryContextLines = buildMemoryContextLines2, renderMemoryContextPrompt = renderMemoryContextPrompt2, isHeartbeatTrigger = isHeartbeatTrigger2, resolveHeartbeatPromptCandidates = resolveHeartbeatPromptCandidates2, matchHeartbeatEntry = matchHeartbeatEntry2, looksLikeHeartbeatPrompt = looksLikeHeartbeatPrompt2;
8056
+ var rememberObservedInboundMessageId = rememberObservedInboundMessageId2, rememberObservedInboundMessageKeys = rememberObservedInboundMessageKeys2, rememberObservedInboundContentFingerprint = rememberObservedInboundContentFingerprint2, rememberPendingSparseInboundContentFingerprint = rememberPendingSparseInboundContentFingerprint2, consumePendingSparseInboundContentFingerprint = consumePendingSparseInboundContentFingerprint2, rememberInboundReplyMetadata = rememberInboundReplyMetadata2, getInboundReplyMetadata = getInboundReplyMetadata2, rememberObservedInlineExplicitCaptureKey = rememberObservedInlineExplicitCaptureKey2, buildInlineExplicitCaptureDedupeKey = buildInlineExplicitCaptureDedupeKey2, resolveStoredCodexThreadId = resolveStoredCodexThreadId2, resolveStoredCodexBufferKey = resolveStoredCodexBufferKey2, addSessionToCodexIndex = addSessionToCodexIndex2, removeSessionFromCodexIndex = removeSessionFromCodexIndex2, resolveCodexCompactionBaselineKey = resolveCodexCompactionBaselineKey2, resolveCodexPromptCacheKey = resolveCodexPromptCacheKey2, rememberCodexThread = rememberCodexThread2, forgetCodexThread = forgetCodexThread2, clearCodexCompatCaches = clearCodexCompatCaches2, hasExplicitProviderIdentity = hasExplicitProviderIdentity2, cachePromptMemoryLines = cachePromptMemoryLines2, consumePromptMemoryLines = consumePromptMemoryLines2, resolveSessionIdentity = resolveSessionIdentity2, hasBufferedTurns = hasBufferedTurns2, resolveExtractionBufferKey = resolveExtractionBufferKey2, renderMemoryContext = renderMemoryContext2, isHeartbeatTrigger = isHeartbeatTrigger2, resolveHeartbeatPromptCandidates = resolveHeartbeatPromptCandidates2, matchHeartbeatEntry = matchHeartbeatEntry2, looksLikeHeartbeatPrompt = looksLikeHeartbeatPrompt2;
7710
8057
  const hooksPolicy = readPluginHooksPolicy(api.config, serviceId);
7711
8058
  const promptInjectionAllowed = coerceRawConfigBoolean(hooksPolicy?.allowPromptInjection) !== false;
7712
8059
  const useMemoryPromptSection = sdkCaps.hasRegisterMemoryPromptSection && typeof api.registerMemoryPromptSection === "function" && promptInjectionAllowed;
@@ -8118,7 +8465,13 @@ Keep the reflection grounded in the evidence below.
8118
8465
  const dreamLines = await loadRecentDreamLines(
8119
8466
  ctx?.workspaceDir
8120
8467
  ).catch(() => []);
8121
- const context = await orchestrator.recall(prompt, sessionKey);
8468
+ let recallComposition;
8469
+ const context = await orchestrator.recall(prompt, sessionKey, {
8470
+ onContextComposition: (composition) => {
8471
+ recallComposition = composition;
8472
+ }
8473
+ });
8474
+ recallComposition ??= { context };
8122
8475
  logger_exports.log.debug(
8123
8476
  `${hookLabel}: recall returned ${context?.length ?? 0} chars`
8124
8477
  );
@@ -8127,7 +8480,7 @@ Keep the reflection grounded in the evidence below.
8127
8480
  const auxiliaryDreamLines = plannerSuppressesAuxiliaryRecall ? [] : dreamLines;
8128
8481
  const auxiliaryActiveRecallLines = plannerSuppressesAuxiliaryRecall ? [] : activeRecallLines;
8129
8482
  const memoryIds = lastRecall?.memoryIds ?? [];
8130
- if (!context) {
8483
+ if (!recallComposition.context && !recallComposition.footer) {
8131
8484
  const auxiliarySummary = summarizeRecallTextForStatus(activeRecallResult?.summary ?? null) ?? summarizeRecallTextForStatus(
8132
8485
  [...auxiliaryDreamLines, ...auxiliaryActiveRecallLines].join(" ")
8133
8486
  ) ?? (verboseRequested ? "Remnic recall metadata injected without matching memory context." : null);
@@ -8179,9 +8532,9 @@ Keep the reflection grounded in the evidence below.
8179
8532
  memoryLines: mergedLines
8180
8533
  };
8181
8534
  }
8182
- const maxChars = cfg.recallBudgetChars;
8183
- if (maxChars === 0) return;
8184
- const trimmed = context.length > maxChars ? context.slice(0, maxChars) + "\n\n...(memory context trimmed)" : context;
8535
+ const memoryContext = renderMemoryContext2(recallComposition);
8536
+ if (!memoryContext) return;
8537
+ const trimmed = memoryContext.body;
8185
8538
  const summaryText = summarizeRecallTextForStatus(activeRecallResult?.summary ?? null) ?? summarizeRecallTextForStatus(trimmed);
8186
8539
  lastRecallSummaryBySession.set(sessionKey, summaryText);
8187
8540
  if (cfg.recallTranscriptsEnabled) {
@@ -8217,9 +8570,9 @@ Keep the reflection grounded in the evidence below.
8217
8570
  ...auxiliaryDreamLines,
8218
8571
  ...auxiliaryActiveRecallLines
8219
8572
  ];
8220
- const memorySectionLines = buildMemoryContextLines2(trimmed);
8573
+ const memorySectionLines = memoryContext.lines;
8221
8574
  const memoryLines = useMemoryPromptSection ? memorySectionLines : [...auxiliaryLines, ...memorySectionLines];
8222
- const promptWithVerbose = useMemoryPromptSection ? auxiliaryLines.length > 0 ? auxiliaryLines.join("\n").replace(/\n$/, "") : void 0 : auxiliaryLines.length > 0 ? [...auxiliaryLines, ...memorySectionLines].join("\n").replace(/\n$/, "") : renderMemoryContextPrompt2(trimmed);
8575
+ const promptWithVerbose = useMemoryPromptSection ? auxiliaryLines.length > 0 ? auxiliaryLines.join("\n").replace(/\n$/, "") : void 0 : auxiliaryLines.length > 0 ? [...auxiliaryLines, ...memorySectionLines].join("\n").replace(/\n$/, "") : memoryContext.prompt;
8223
8576
  logger_exports.log.debug(
8224
8577
  `${hookLabel}: returning memory context with ${trimmed.length} chars`
8225
8578
  );