@remnic/plugin-openclaw 9.69.60 → 9.69.63

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
@@ -19,7 +19,7 @@ import OpenAI from "openai";
19
19
  import { createRequire } from "module";
20
20
  import { createHash as createHash3 } from "crypto";
21
21
  import { parseConfig } from "@remnic/core/config";
22
- import { initLogger, log as log8 } from "@remnic/core/logger";
22
+ import { initLogger, log as log9 } from "@remnic/core/logger";
23
23
  import {
24
24
  detectSdkCapabilities
25
25
  } from "@remnic/core/sdk-compat";
@@ -450,7 +450,117 @@ function formatContinuityLoopSummary(loop) {
450
450
  if (loop.notes) lines.push(`Notes: ${loop.notes}`);
451
451
  return lines.join("\n");
452
452
  }
453
- function registerTools(api, orchestrator, hostRuntimeAgentId) {
453
+ function buildLegacyMemorySearchTool(orchestrator) {
454
+ return {
455
+ name: "memory_search",
456
+ label: "Search Memory",
457
+ description: `Search local memory files using QMD's semantic index. Returns matching memories with snippets and relevance scores.
458
+
459
+ Returns: Matching memory entries ranked by relevance
460
+ Cost: Free (local index query)
461
+ Speed: Fast
462
+
463
+ Best for:
464
+ - Finding previously learned facts about the user
465
+ - Checking what you know about a topic
466
+ - Locating past decisions or corrections`,
467
+ parameters: Type2.Object({
468
+ query: Type2.String({
469
+ description: "Search query \u2014 keywords, phrases, or natural language"
470
+ }),
471
+ namespace: Type2.Optional(
472
+ Type2.String({
473
+ description: "Optional namespace filter. When set, only returns results under memoryDir/namespaces/<namespace>/ (default namespace uses legacy root)."
474
+ })
475
+ ),
476
+ maxResults: Type2.Optional(
477
+ Type2.Number({
478
+ description: "Maximum results (default: 8)",
479
+ minimum: 1,
480
+ maximum: 50
481
+ })
482
+ ),
483
+ collection: Type2.Optional(
484
+ Type2.String({
485
+ description: "QMD collection to search. Omit for memory collection, use 'global' for all collections."
486
+ })
487
+ )
488
+ }),
489
+ async execute(_toolCallId, params) {
490
+ const { query, maxResults, collection, namespace } = params;
491
+ const namespaceFilter = namespace && namespace.length > 0 ? namespace : void 0;
492
+ const resultLimit = normalizeMemorySearchResultLimit(maxResults);
493
+ const searchCandidates = async (limit) => collection === "global" && !namespaceFilter ? await orchestrator.qmd.searchGlobal(query, limit) : await orchestrator.searchAcrossNamespaces({
494
+ query,
495
+ namespaces: namespaceFilter ? [namespaceFilter] : void 0,
496
+ maxResults: limit,
497
+ mode: "search"
498
+ });
499
+ let candidateLimit = resultLimit;
500
+ const privateVisibilityCache = /* @__PURE__ */ new Map();
501
+ let candidates = await searchCandidates(candidateLimit);
502
+ let filtered = await orchestrator.filterPrivateSearchResults(
503
+ candidates,
504
+ namespaceFilter ? [namespaceFilter] : [],
505
+ false,
506
+ privateVisibilityCache
507
+ );
508
+ while (filtered.length < resultLimit && candidates.length >= candidateLimit && candidateLimit < MEMORY_SEARCH_CANDIDATE_CAP) {
509
+ const nextCandidateLimit = Math.min(
510
+ MEMORY_SEARCH_CANDIDATE_CAP,
511
+ Math.max(candidateLimit + 16, candidateLimit * 2)
512
+ );
513
+ if (nextCandidateLimit === candidateLimit) break;
514
+ const nextCandidates = await searchCandidates(nextCandidateLimit);
515
+ if (nextCandidates.length <= candidates.length) break;
516
+ candidateLimit = nextCandidateLimit;
517
+ candidates = nextCandidates;
518
+ filtered = await orchestrator.filterPrivateSearchResults(
519
+ candidates,
520
+ namespaceFilter ? [namespaceFilter] : [],
521
+ false,
522
+ privateVisibilityCache
523
+ );
524
+ }
525
+ filtered = filtered.slice(0, resultLimit);
526
+ if (filtered.length === 0) {
527
+ return toolResult2(`No memories found matching: "${query}"`);
528
+ }
529
+ const formatted = filtered.map((r, i) => {
530
+ const snippet = r.snippet ? r.snippet.slice(0, 800) : "(no preview)";
531
+ return `### [${i + 1}] ${r.path}
532
+ Score: ${r.score.toFixed(3)}
533
+
534
+ \`\`\`
535
+ ${snippet}
536
+ \`\`\``;
537
+ }).join("\n\n");
538
+ return toolResult2(
539
+ `## Memory Search: "${query}"
540
+
541
+ ${filtered.length} result(s)
542
+
543
+ ${formatted}`
544
+ );
545
+ }
546
+ };
547
+ }
548
+ function buildLegacyMemorySearchToolForPublicShape(orchestrator) {
549
+ const legacy = buildLegacyMemorySearchTool(orchestrator);
550
+ return {
551
+ ...legacy,
552
+ async execute(toolCallId, params) {
553
+ const filters = params.filters && typeof params.filters === "object" ? params.filters : void 0;
554
+ return legacy.execute(toolCallId, {
555
+ query: params.query,
556
+ ...typeof params.limit === "number" ? { maxResults: params.limit } : {},
557
+ ...typeof filters?.namespace === "string" ? { namespace: filters.namespace } : {},
558
+ ...typeof filters?.collection === "string" ? { collection: filters.collection } : {}
559
+ });
560
+ }
561
+ };
562
+ }
563
+ function registerTools(api, orchestrator, hostRuntimeAgentId, reservedToolNames = []) {
454
564
  const useDedicatedOpenClawMemoryTools = orchestrator.config.openclawToolsEnabled !== false;
455
565
  const actionTypes = [
456
566
  "store_episode",
@@ -551,103 +661,8 @@ Content: ${content}`
551
661
  }
552
662
  }
553
663
  }
554
- if (!useDedicatedOpenClawMemoryTools) {
555
- api.registerTool(
556
- {
557
- name: "memory_search",
558
- label: "Search Memory",
559
- description: `Search local memory files using QMD's semantic index. Returns matching memories with snippets and relevance scores.
560
-
561
- Returns: Matching memory entries ranked by relevance
562
- Cost: Free (local index query)
563
- Speed: Fast
564
-
565
- Best for:
566
- - Finding previously learned facts about the user
567
- - Checking what you know about a topic
568
- - Locating past decisions or corrections`,
569
- parameters: Type2.Object({
570
- query: Type2.String({
571
- description: "Search query \u2014 keywords, phrases, or natural language"
572
- }),
573
- namespace: Type2.Optional(
574
- Type2.String({
575
- description: "Optional namespace filter. When set, only returns results under memoryDir/namespaces/<namespace>/ (default namespace uses legacy root)."
576
- })
577
- ),
578
- maxResults: Type2.Optional(
579
- Type2.Number({
580
- description: "Maximum results (default: 8)",
581
- minimum: 1,
582
- maximum: 50
583
- })
584
- ),
585
- collection: Type2.Optional(
586
- Type2.String({
587
- description: "QMD collection to search. Omit for memory collection, use 'global' for all collections."
588
- })
589
- )
590
- }),
591
- async execute(_toolCallId, params) {
592
- const { query, maxResults, collection, namespace } = params;
593
- const namespaceFilter = namespace && namespace.length > 0 ? namespace : void 0;
594
- const resultLimit = normalizeMemorySearchResultLimit(maxResults);
595
- const searchCandidates = async (limit) => collection === "global" && !namespaceFilter ? await orchestrator.qmd.searchGlobal(query, limit) : await orchestrator.searchAcrossNamespaces({
596
- query,
597
- namespaces: namespaceFilter ? [namespaceFilter] : void 0,
598
- maxResults: limit,
599
- mode: "search"
600
- });
601
- let candidateLimit = resultLimit;
602
- const privateVisibilityCache = /* @__PURE__ */ new Map();
603
- let candidates = await searchCandidates(candidateLimit);
604
- let filtered = await orchestrator.filterPrivateSearchResults(
605
- candidates,
606
- namespaceFilter ? [namespaceFilter] : [],
607
- false,
608
- privateVisibilityCache
609
- );
610
- while (filtered.length < resultLimit && candidates.length >= candidateLimit && candidateLimit < MEMORY_SEARCH_CANDIDATE_CAP) {
611
- const nextCandidateLimit = Math.min(
612
- MEMORY_SEARCH_CANDIDATE_CAP,
613
- Math.max(candidateLimit + 16, candidateLimit * 2)
614
- );
615
- if (nextCandidateLimit === candidateLimit) break;
616
- const nextCandidates = await searchCandidates(nextCandidateLimit);
617
- if (nextCandidates.length <= candidates.length) break;
618
- candidateLimit = nextCandidateLimit;
619
- candidates = nextCandidates;
620
- filtered = await orchestrator.filterPrivateSearchResults(
621
- candidates,
622
- namespaceFilter ? [namespaceFilter] : [],
623
- false,
624
- privateVisibilityCache
625
- );
626
- }
627
- filtered = filtered.slice(0, resultLimit);
628
- if (filtered.length === 0) {
629
- return toolResult2(`No memories found matching: "${query}"`);
630
- }
631
- const formatted = filtered.map((r, i) => {
632
- const snippet = r.snippet ? r.snippet.slice(0, 800) : "(no preview)";
633
- return `### [${i + 1}] ${r.path}
634
- Score: ${r.score.toFixed(3)}
635
-
636
- \`\`\`
637
- ${snippet}
638
- \`\`\``;
639
- }).join("\n\n");
640
- return toolResult2(
641
- `## Memory Search: "${query}"
642
-
643
- ${filtered.length} result(s)
644
-
645
- ${formatted}`
646
- );
647
- }
648
- },
649
- { name: "memory_search" }
650
- );
664
+ if (!useDedicatedOpenClawMemoryTools && !reservedToolNames.includes("memory_search")) {
665
+ api.registerTool(buildLegacyMemorySearchTool(orchestrator), { name: "memory_search" });
651
666
  }
652
667
  api.registerTool(
653
668
  {
@@ -3026,8 +3041,8 @@ var EngramAccessService = class extends CoreEngramAccessService {
3026
3041
 
3027
3042
  // ../../src/index.ts
3028
3043
  import { EngramAccessHttpServer } from "@remnic/core/access-http";
3029
- import path11 from "path";
3030
- import os from "os";
3044
+ import path13 from "path";
3045
+ import os2 from "os";
3031
3046
  import { createOpikExporter } from "@remnic/core/opik-exporter";
3032
3047
  import { readEnvVar, resolveHomeDir as resolveHomeDir3 } from "@remnic/core/runtime/env";
3033
3048
  import { displayErrorDetail } from "@remnic/core/runtime/better-sqlite";
@@ -5232,11 +5247,11 @@ async function processOpenClawFlushPlanFile(params) {
5232
5247
  }
5233
5248
 
5234
5249
  // src/delegate-runtime.ts
5235
- import path9 from "path";
5250
+ import path11 from "path";
5236
5251
  import {
5237
5252
  renderMemoryContextPrompt
5238
5253
  } from "@remnic/core";
5239
- import { log as log7 } from "@remnic/core/logger";
5254
+ import { log as log8 } from "@remnic/core/logger";
5240
5255
 
5241
5256
  // src/delegate-authorization.ts
5242
5257
  import { log as log2 } from "@remnic/core/logger";
@@ -5244,10 +5259,62 @@ import { log as log2 } from "@remnic/core/logger";
5244
5259
  // src/bridge.ts
5245
5260
  import fs3 from "fs";
5246
5261
  import path6 from "path";
5247
- import { isIPv6 } from "net";
5262
+
5263
+ // src/bridge-daemon-host.ts
5264
+ import { isIP, isIPv6 } from "net";
5265
+ import os from "os";
5266
+ import { isLoopbackHost } from "@remnic/core/runtime/http-transport.js";
5267
+ var LOOPBACK_V4 = "127.0.0.1";
5268
+ function isLoopbackDaemonHost(host) {
5269
+ const normalized = host.trim().toLowerCase().replace(/^\[/, "").replace(/\]$/, "");
5270
+ if (loopbackForSameHost(normalized) !== void 0) return true;
5271
+ return isLoopbackHost(normalized);
5272
+ }
5273
+ function canonicalIPv6(value) {
5274
+ if (!isIPv6(value)) return void 0;
5275
+ try {
5276
+ return new URL(`http://[${value}]`).hostname.replace(/^\[/, "").replace(/\]$/, "");
5277
+ } catch {
5278
+ return value;
5279
+ }
5280
+ }
5281
+ function loopbackForSameHost(host) {
5282
+ const normalized = host.trim().toLowerCase().replace(/^\[/, "").replace(/\]$/, "");
5283
+ if (normalized === "0.0.0.0") return LOOPBACK_V4;
5284
+ const v6 = canonicalIPv6(normalized);
5285
+ if (v6 === "::") return "::1";
5286
+ const local = localInterfaceFamily(v6 ?? normalized);
5287
+ if (local === void 0) return void 0;
5288
+ return local === "IPv4" ? LOOPBACK_V4 : "::1";
5289
+ }
5290
+ function sameHostDialFallback(host) {
5291
+ const normalized = host.trim().toLowerCase().replace(/^\[/, "").replace(/\]$/, "");
5292
+ return localInterfaceFamily(canonicalIPv6(normalized) ?? normalized) === void 0 ? void 0 : normalized;
5293
+ }
5294
+ function localInterfaceFamily(address) {
5295
+ if (isIP(address) === 0) return void 0;
5296
+ for (const entries of Object.values(os.networkInterfaces())) {
5297
+ for (const entry of entries ?? []) {
5298
+ if (entry.internal) continue;
5299
+ if (entry.family === "IPv6") {
5300
+ if (canonicalIPv6(entry.address.replace(/%.*$/, "")) === address) return "IPv6";
5301
+ continue;
5302
+ }
5303
+ if (entry.address === address || canonicalIPv6(`::ffff:${entry.address}`) === address) {
5304
+ return "IPv4";
5305
+ }
5306
+ }
5307
+ }
5308
+ return void 0;
5309
+ }
5310
+ function normalizeDaemonHost(value) {
5311
+ const match = value.trim().match(/^\[(.+)\]$/);
5312
+ return match ? match[1] : value.trim();
5313
+ }
5314
+
5315
+ // src/bridge.ts
5248
5316
  import { Worker } from "worker_threads";
5249
5317
  import { configPathCandidates, readCompatEnv } from "@remnic/core";
5250
- import { isLoopbackHost } from "@remnic/core/runtime/http-transport.js";
5251
5318
 
5252
5319
  // src/bridge-health-worker.ts
5253
5320
  function runHealthWorker(request, data) {
@@ -6047,29 +6114,6 @@ function isDaemonRunning() {
6047
6114
  }
6048
6115
  return false;
6049
6116
  }
6050
- function isLoopbackDaemonHost(host) {
6051
- const normalized = host.trim().toLowerCase().replace(/^\[/, "").replace(/\]$/, "");
6052
- if (loopbackForWildcardBind(normalized) !== void 0) return true;
6053
- return isLoopbackHost(normalized);
6054
- }
6055
- function canonicalIPv6(value) {
6056
- if (!isIPv6(value)) return void 0;
6057
- try {
6058
- return new URL(`http://[${value}]`).hostname.replace(/^\[/, "").replace(/\]$/, "");
6059
- } catch {
6060
- return value;
6061
- }
6062
- }
6063
- function loopbackForWildcardBind(host) {
6064
- const normalized = host.trim().toLowerCase().replace(/^\[/, "").replace(/\]$/, "");
6065
- if (normalized === "0.0.0.0") return DEFAULT_HOST;
6066
- if (canonicalIPv6(normalized) === "::") return "::1";
6067
- return void 0;
6068
- }
6069
- function normalizeDaemonHost(value) {
6070
- const match = value.trim().match(/^\[(.+)\]$/);
6071
- return match ? match[1] : value.trim();
6072
- }
6073
6117
  function coerceDaemonPort(value) {
6074
6118
  const parsed = typeof value === "string" && value.trim() !== "" ? Number(value.trim()) : value;
6075
6119
  return typeof parsed === "number" && Number.isInteger(parsed) && parsed > 0 && parsed <= 65535 ? parsed : void 0;
@@ -6158,7 +6202,7 @@ function shouldProbeDaemonHealth(host) {
6158
6202
  }
6159
6203
  function readDaemonHost() {
6160
6204
  const resolved = readConfiguredDaemonHost();
6161
- return loopbackForWildcardBind(resolved) ?? resolved;
6205
+ return loopbackForSameHost(resolved) ?? resolved;
6162
6206
  }
6163
6207
  function readDaemonServerConfig() {
6164
6208
  for (const candidate of configPathCandidates()) {
@@ -6196,39 +6240,40 @@ function daemonEndpointCandidates(unitExists) {
6196
6240
  const resolvedHost = normalizeDaemonHost(
6197
6241
  envHost !== void 0 && envHost.trim() !== "" ? envHost : host ?? DEFAULT_HOST
6198
6242
  );
6199
- const dialHost = loopbackForWildcardBind(resolvedHost) ?? resolvedHost;
6243
+ const dialHost = loopbackForSameHost(resolvedHost) ?? resolvedHost;
6244
+ const dialFallback = sameHostDialFallback(resolvedHost);
6200
6245
  const dialPort = envPort ?? port ?? DEFAULT_PORT;
6201
6246
  const token = authTokenOverride ?? loadDaemonAuth(configPath).token;
6202
6247
  const configToken = configPath === void 0 ? void 0 : readServerBlock(configPath)?.authToken;
6203
6248
  const fallbackToken = configToken !== void 0 && configToken !== token ? configToken : void 0;
6204
- if (candidates.some(
6205
- (c) => c.host === dialHost && c.port === dialPort && c.token === token && // The BOUND credential is part of the identity too: when a gateway
6206
- // token wins for both, two configs on one endpoint resolve the same
6207
- // primary token but carry different fallbacks, and dropping the
6208
- // second would leave the daemon's real credential untried.
6209
- c.fallbackToken === fallbackToken && // So is the UNIT the credential is re-read from per request: two
6210
- // units can agree today and diverge on the next rotation.
6211
- c.authTokenUnit?.unitPath === authTokenUnit?.unitPath && // And so is the CONFIG, for the same reason: `daemonConfigPath` is
6212
- // re-read per request, so collapsing two configs that agree today
6213
- // would keep sending the retained one's token after the other
6214
- // rotates.
6215
- c.configPath === configPath
6216
- )) {
6217
- return;
6249
+ for (const candidateHost of dialFallback === void 0 ? [dialHost] : [dialHost, dialFallback]) {
6250
+ if (candidates.some(
6251
+ (c) => c.host === candidateHost && c.port === dialPort && c.token === token && // The BOUND credential is part of the identity too: when a gateway
6252
+ // token wins for both, two configs on one endpoint resolve the same
6253
+ // primary token but carry different fallbacks, and dropping the
6254
+ // second would leave the daemon's real credential untried.
6255
+ c.fallbackToken === fallbackToken && // So is the UNIT the credential is re-read from per request: two
6256
+ // units can agree today and diverge on the next rotation.
6257
+ c.authTokenUnit?.unitPath === authTokenUnit?.unitPath && // And so is the CONFIG, for the same reason: `daemonConfigPath` is
6258
+ // re-read per request, so collapsing two configs that agree today
6259
+ // would keep sending the retained one's token after the other
6260
+ // rotates.
6261
+ c.configPath === configPath
6262
+ )) {
6263
+ continue;
6264
+ }
6265
+ candidates.push({
6266
+ host: candidateHost,
6267
+ port: dialPort,
6268
+ configPath,
6269
+ token,
6270
+ aliasGroup: `${resolvedHost}\0${dialPort}\0${configPath ?? ""}\0${token}`,
6271
+ ...authTokenOverride === void 0 ? {} : { authTokenOverride },
6272
+ ...authTokenUnit === void 0 ? {} : { authTokenUnit },
6273
+ ...fallbackToken === void 0 ? {} : { fallbackToken }
6274
+ });
6218
6275
  }
6219
- candidates.push({
6220
- host: dialHost,
6221
- port: dialPort,
6222
- configPath,
6223
- token,
6224
- ...authTokenOverride === void 0 ? {} : { authTokenOverride },
6225
- ...authTokenUnit === void 0 ? {} : { authTokenUnit },
6226
- ...fallbackToken === void 0 ? {} : { fallbackToken }
6227
- });
6228
6276
  };
6229
- if (envHost !== void 0 && envHost.trim() !== "" || envPort !== void 0) {
6230
- add(void 0, void 0);
6231
- }
6232
6277
  const configOrder = configPathCandidates();
6233
6278
  const envConfigPath = readCompatEnv("REMNIC_CONFIG_PATH", "ENGRAM_CONFIG_PATH") ? configOrder[0] : void 0;
6234
6279
  const addConfigCandidate = (candidate) => {
@@ -6294,14 +6339,16 @@ function detectDaemonBridgeMode(options) {
6294
6339
  }
6295
6340
  probeable.push(candidate);
6296
6341
  }
6297
- let probed = 0;
6342
+ const groupDeadlines = /* @__PURE__ */ new Map();
6343
+ const totalGroups = new Set(probeable.map((candidate) => candidate.aliasGroup)).size;
6298
6344
  for (const {
6299
6345
  host: daemonHost,
6300
6346
  port: daemonPort,
6301
6347
  configPath,
6302
6348
  authTokenOverride,
6303
6349
  authTokenUnit,
6304
- fallbackToken
6350
+ fallbackToken,
6351
+ aliasGroup
6305
6352
  } of probeable) {
6306
6353
  const remainingMs = deadline - Date.now();
6307
6354
  if (remainingMs <= 0) {
@@ -6310,12 +6357,15 @@ function detectDaemonBridgeMode(options) {
6310
6357
  );
6311
6358
  break;
6312
6359
  }
6313
- const perCandidateMs = Math.max(
6314
- 1,
6315
- Math.ceil(remainingMs / Math.max(1, probeable.length - probed))
6316
- );
6317
- probed += 1;
6318
- const candidateDeadline = Date.now() + Math.min(remainingMs, perCandidateMs);
6360
+ const started = groupDeadlines.get(aliasGroup);
6361
+ if (started === void 0) {
6362
+ const perGroupMs = Math.max(
6363
+ 1,
6364
+ Math.ceil(remainingMs / Math.max(1, totalGroups - groupDeadlines.size))
6365
+ );
6366
+ groupDeadlines.set(aliasGroup, Date.now() + Math.min(remainingMs, perGroupMs));
6367
+ }
6368
+ const candidateDeadline = groupDeadlines.get(aliasGroup) ?? Date.now();
6319
6369
  const firstAttemptMs = candidateDeadline - Date.now();
6320
6370
  if (firstAttemptMs <= 0) {
6321
6371
  options.onSkip?.(
@@ -6421,10 +6471,13 @@ function resolveBridgeMode(configBridgeMode, options = {}) {
6421
6471
  });
6422
6472
  }
6423
6473
  const selectedConfig = selectedDaemonConfigPath();
6474
+ const configuredHost = readConfiguredDaemonHost();
6475
+ const fallbackHost = sameHostDialFallback(configuredHost);
6424
6476
  return {
6425
6477
  mode: requested,
6426
- daemonHost: readDaemonHost(),
6478
+ daemonHost: loopbackForSameHost(configuredHost) ?? configuredHost,
6427
6479
  daemonPort: readDaemonPort(),
6480
+ ...fallbackHost === void 0 ? {} : { daemonHostFallback: fallbackHost },
6428
6481
  ...selectedConfig === void 0 ? {} : { daemonConfigPath: selectedConfig }
6429
6482
  };
6430
6483
  }
@@ -6603,114 +6656,234 @@ async function probeDelegateAuthorization(target, namespace = "", operations = D
6603
6656
  return { state: "unavailable", tokenSource: auth.source };
6604
6657
  }
6605
6658
 
6606
- // src/delegate-runtime.ts
6659
+ // src/delegate-namespace-bindings.ts
6660
+ import path7 from "path";
6661
+ import { log as log3 } from "@remnic/core/logger";
6607
6662
  import {
6608
6663
  SESSION_NAMESPACE_BINDING_MAX_ENTRIES,
6609
- SESSION_NAMESPACE_BINDING_MAX_NAMESPACES as SESSION_NAMESPACE_BINDING_MAX_NAMESPACES2,
6664
+ SESSION_NAMESPACE_BINDING_MAX_NAMESPACES,
6610
6665
  createFileSessionNamespaceBindingStore
6611
6666
  } from "@remnic/core/session-namespace-bindings";
6612
-
6613
- // src/delegate-namespaces.ts
6614
- import {
6615
- SESSION_NAMESPACE_BINDING_MAX_NAMESPACE_LENGTH
6616
- } from "@remnic/core/session-namespace-bindings";
6617
- import { log as log3 } from "@remnic/core/logger";
6618
- async function withNamespace(namespace, body, resolveScopedNamespace) {
6619
- const scoped = await resolveScopedNamespace(namespace || void 0);
6620
- return scoped === void 0 ? body : { ...body, namespace: scoped };
6621
- }
6622
- function explicitSessionNamespaceFrom(sessionKey, event, ctx) {
6623
- const eventSessionKey = typeof event.sessionKey === "string" ? event.sessionKey : void 0;
6624
- const ctxSessionKey = typeof ctx.sessionKey === "string" ? ctx.sessionKey : void 0;
6625
- const sources = eventSessionKey === sessionKey ? [event, ctx] : ctxSessionKey === sessionKey ? [ctx, event] : [ctx, event];
6626
- for (const source of sources) {
6627
- const sourceSessionKey = typeof source.sessionKey === "string" ? source.sessionKey : void 0;
6628
- if (sourceSessionKey !== sessionKey) continue;
6629
- const runtime = source.runtime;
6630
- if (typeof runtime !== "object" || runtime === null) continue;
6631
- const agent = runtime.agent;
6632
- if (typeof agent !== "object" || agent === null) continue;
6633
- const session = agent.session;
6634
- if (typeof session !== "object" || session === null) continue;
6635
- const namespace = session.namespace;
6636
- if (namespace !== void 0 && typeof namespace !== "string") {
6637
- throw new Error("delegate session namespace metadata must be a string");
6638
- }
6639
- return { namespace: typeof namespace === "string" ? namespace.trim() || void 0 : void 0 };
6640
- }
6641
- return void 0;
6642
- }
6643
- async function rememberedNamespacesFor(sessionKey, namespaceBindings) {
6644
- return namespaceBindings.namespacesFor(sessionKey);
6645
- }
6646
- async function rememberNamespace(sessionKey, namespace, namespaceBindings) {
6647
- if (namespace.length > SESSION_NAMESPACE_BINDING_MAX_NAMESPACE_LENGTH) {
6648
- throw new Error(
6649
- `delegate session namespace exceeds the daemon limit of ${SESSION_NAMESPACE_BINDING_MAX_NAMESPACE_LENGTH} characters`
6650
- );
6651
- }
6652
- try {
6653
- await namespaceBindings.remember(sessionKey, namespace);
6654
- } catch (err) {
6655
- log3.warn(`delegate namespace binding persistence failed: ${String(err)}`);
6656
- throw err;
6657
- }
6658
- }
6659
- async function sessionNamespaceFrom(sessionKey, event, ctx, fallback, namespaceBindings) {
6660
- const explicit = explicitSessionNamespaceFrom(sessionKey, event, ctx);
6661
- if (explicit !== void 0) {
6662
- await rememberNamespace(sessionKey, explicit.namespace ?? "", namespaceBindings);
6663
- return explicit.namespace;
6664
- }
6665
- const remembered = await rememberedNamespacesFor(sessionKey, namespaceBindings);
6666
- return remembered.length > 0 ? remembered.at(-1) || void 0 : fallback.trim() || void 0;
6667
- }
6668
- async function lifecycleSessionNamespacesFrom(sessionKey, event, ctx, fallback, namespaceBindings) {
6669
- const explicit = explicitSessionNamespaceFrom(sessionKey, event, ctx);
6670
- if (explicit !== void 0) {
6671
- await rememberNamespace(sessionKey, explicit.namespace ?? "", namespaceBindings);
6672
- }
6673
- const remembered = await rememberedNamespacesFor(sessionKey, namespaceBindings);
6674
- if (explicit !== void 0) {
6675
- const explicitNamespace = explicit.namespace ?? "";
6676
- const namespaces = remembered.includes(explicitNamespace) ? remembered : [...remembered, explicitNamespace];
6677
- return namespaces.map((namespace) => namespace || void 0);
6667
+ var delegateNamespaceMigrationChains = /* @__PURE__ */ new Map();
6668
+ var queueDelegateNamespaceMigration = (bindingPath, sessionKey, operation) => {
6669
+ let sessionChains = delegateNamespaceMigrationChains.get(bindingPath);
6670
+ if (sessionChains === void 0) {
6671
+ sessionChains = /* @__PURE__ */ new Map();
6672
+ delegateNamespaceMigrationChains.set(bindingPath, sessionChains);
6678
6673
  }
6679
- if (remembered.length > 0) return remembered.map((namespace) => namespace || void 0);
6680
- return [fallback.trim() || void 0];
6681
- }
6682
-
6683
- // src/delegate-daemon-target.ts
6684
- function daemonTargetFor(bridge) {
6685
- const invalidatedCredentials = /* @__PURE__ */ new Set();
6686
- const invalidated = (token) => invalidatedCredentials.has(`daemon configuration\0${token}`);
6687
- return {
6688
- host: bridge.daemonHost,
6689
- port: bridge.daemonPort,
6690
- invalidateAuthToken: (auth) => {
6691
- invalidatedCredentials.add(`${auth.source}\0${auth.token}`);
6692
- },
6693
- resolveAuthToken: () => {
6694
- if (bridge.daemonAuthTokenOverride !== void 0 && !invalidated(bridge.daemonAuthTokenOverride)) {
6695
- const unit = bridge.daemonAuthUnit === void 0 ? void 0 : readUnitAuthToken(bridge.daemonAuthUnit);
6696
- if (unit?.readable === true && unit.token !== void 0 && !invalidated(unit.token)) {
6697
- return { token: unit.token, source: "daemon configuration" };
6698
- }
6699
- if ((unit === void 0 || unit.readable === false) && !invalidated(bridge.daemonAuthTokenOverride)) {
6700
- return { token: bridge.daemonAuthTokenOverride, source: "daemon configuration" };
6701
- }
6702
- if (bridge.daemonConfigPath !== void 0) {
6703
- const configToken = readDaemonConfigAuthToken(bridge.daemonConfigPath);
6704
- if (configToken !== void 0 && !invalidated(configToken)) {
6705
- return { token: configToken, source: "daemon configuration" };
6706
- }
6707
- }
6708
- }
6709
- if (bridge.daemonAuthPrefersConfig && bridge.daemonConfigPath !== void 0) {
6710
- const configToken = readDaemonConfigAuthToken(bridge.daemonConfigPath);
6711
- if (configToken !== void 0 && !invalidated(configToken)) {
6712
- return { token: configToken, source: "daemon configuration" };
6713
- }
6674
+ const prior = sessionChains.get(sessionKey) ?? Promise.resolve();
6675
+ const run = prior.catch(() => void 0).then(operation);
6676
+ const settled = run.then(
6677
+ () => void 0,
6678
+ () => void 0
6679
+ );
6680
+ sessionChains.set(sessionKey, settled);
6681
+ void settled.then(() => {
6682
+ if (sessionChains?.get(sessionKey) !== settled) return;
6683
+ sessionChains.delete(sessionKey);
6684
+ if (sessionChains.size === 0 && delegateNamespaceMigrationChains.get(bindingPath) === sessionChains) {
6685
+ delegateNamespaceMigrationChains.delete(bindingPath);
6686
+ }
6687
+ });
6688
+ return run;
6689
+ };
6690
+ function createDelegateNamespaceBindingStore(memoryDir, serviceId, isLegacyAdapterActive) {
6691
+ const bindingPath = (pluginId) => path7.join(memoryDir, "state", "plugins", pluginId, "session-namespace-bindings.json");
6692
+ const primaryPath = bindingPath(serviceId);
6693
+ const primary = createFileSessionNamespaceBindingStore(primaryPath);
6694
+ if (serviceId !== REMNIC_OPENCLAW_PLUGIN_ID) return primary;
6695
+ const legacy = createFileSessionNamespaceBindingStore(bindingPath(REMNIC_OPENCLAW_LEGACY_PLUGIN_ID));
6696
+ const migratedLegacySessions = /* @__PURE__ */ new Set();
6697
+ const rememberMigratedLegacySession = (sessionKey) => {
6698
+ if (migratedLegacySessions.has(sessionKey)) return;
6699
+ migratedLegacySessions.add(sessionKey);
6700
+ while (migratedLegacySessions.size > SESSION_NAMESPACE_BINDING_MAX_ENTRIES) {
6701
+ const oldest = migratedLegacySessions.values().next().value;
6702
+ if (oldest === void 0) return;
6703
+ migratedLegacySessions.delete(oldest);
6704
+ }
6705
+ };
6706
+ const queueSessionMigration = (sessionKey, operation) => queueDelegateNamespaceMigration(primaryPath, sessionKey, operation);
6707
+ const readLegacyNamespaces = async (sessionKey, current) => {
6708
+ if (!isLegacyAdapterActive() && migratedLegacySessions.has(sessionKey)) return [];
6709
+ try {
6710
+ const previous = await legacy.namespacesFor(sessionKey);
6711
+ if (previous.length === 0) rememberMigratedLegacySession(sessionKey);
6712
+ return previous;
6713
+ } catch (err) {
6714
+ if (current.length > 0) {
6715
+ log3.warn(`[${serviceId}] delegate legacy namespace read failed; using canonical bindings: ${String(err)}`);
6716
+ return [];
6717
+ }
6718
+ throw err;
6719
+ }
6720
+ };
6721
+ const mergeNamespaceHistory = (current, previous) => {
6722
+ const merged = [];
6723
+ for (const remembered of [...previous, ...current]) {
6724
+ const existing = merged.indexOf(remembered);
6725
+ if (existing >= 0) merged.splice(existing, 1);
6726
+ merged.push(remembered);
6727
+ }
6728
+ return merged.slice(-SESSION_NAMESPACE_BINDING_MAX_NAMESPACES);
6729
+ };
6730
+ const persistNamespaceHistory = async (store, sessionKey, namespaces) => {
6731
+ if (store.replace !== void 0) {
6732
+ await store.replace(sessionKey, namespaces);
6733
+ return;
6734
+ }
6735
+ for (const namespace of namespaces) {
6736
+ await store.remember(sessionKey, namespace);
6737
+ }
6738
+ };
6739
+ const completeLegacyMigration = async (sessionKey) => {
6740
+ if (!isLegacyAdapterActive()) {
6741
+ try {
6742
+ await legacy.replace?.(sessionKey, []);
6743
+ } catch (err) {
6744
+ log3.warn(`[${serviceId}] delegate legacy namespace cleanup failed: ${String(err)}`);
6745
+ }
6746
+ }
6747
+ rememberMigratedLegacySession(sessionKey);
6748
+ };
6749
+ return {
6750
+ async namespacesFor(sessionKey) {
6751
+ return queueSessionMigration(sessionKey, async () => {
6752
+ const current = await primary.namespacesFor(sessionKey);
6753
+ const previous = await readLegacyNamespaces(sessionKey, current);
6754
+ if (previous.length === 0) return current;
6755
+ const merged = mergeNamespaceHistory(current, previous);
6756
+ const hasMissingLegacy = previous.some((remembered) => !current.includes(remembered));
6757
+ if (!hasMissingLegacy) {
6758
+ await completeLegacyMigration(sessionKey);
6759
+ return current;
6760
+ }
6761
+ try {
6762
+ await persistNamespaceHistory(primary, sessionKey, merged);
6763
+ await completeLegacyMigration(sessionKey);
6764
+ } catch (err) {
6765
+ log3.warn(`[${serviceId}] delegate namespace migration failed: ${String(err)}`);
6766
+ }
6767
+ return merged;
6768
+ });
6769
+ },
6770
+ async remember(sessionKey, namespace) {
6771
+ return queueSessionMigration(sessionKey, async () => {
6772
+ const current = await primary.namespacesFor(sessionKey);
6773
+ const previous = await readLegacyNamespaces(sessionKey, current);
6774
+ if (previous.length === 0) {
6775
+ await primary.remember(sessionKey, namespace);
6776
+ return;
6777
+ }
6778
+ const merged = mergeNamespaceHistory([...current, namespace], previous);
6779
+ await persistNamespaceHistory(primary, sessionKey, merged);
6780
+ await completeLegacyMigration(sessionKey);
6781
+ });
6782
+ }
6783
+ };
6784
+ }
6785
+
6786
+ // src/delegate-namespaces.ts
6787
+ import {
6788
+ SESSION_NAMESPACE_BINDING_MAX_NAMESPACE_LENGTH
6789
+ } from "@remnic/core/session-namespace-bindings";
6790
+ import { log as log4 } from "@remnic/core/logger";
6791
+ async function withNamespace(namespace, body, resolveScopedNamespace) {
6792
+ const scoped = await resolveScopedNamespace(namespace || void 0);
6793
+ return scoped === void 0 ? body : { ...body, namespace: scoped };
6794
+ }
6795
+ function explicitSessionNamespaceFrom(sessionKey, event, ctx) {
6796
+ const eventSessionKey = typeof event.sessionKey === "string" ? event.sessionKey : void 0;
6797
+ const ctxSessionKey = typeof ctx.sessionKey === "string" ? ctx.sessionKey : void 0;
6798
+ const sources = eventSessionKey === sessionKey ? [event, ctx] : ctxSessionKey === sessionKey ? [ctx, event] : [ctx, event];
6799
+ for (const source of sources) {
6800
+ const sourceSessionKey = typeof source.sessionKey === "string" ? source.sessionKey : void 0;
6801
+ if (sourceSessionKey !== sessionKey) continue;
6802
+ const runtime = source.runtime;
6803
+ if (typeof runtime !== "object" || runtime === null) continue;
6804
+ const agent = runtime.agent;
6805
+ if (typeof agent !== "object" || agent === null) continue;
6806
+ const session = agent.session;
6807
+ if (typeof session !== "object" || session === null) continue;
6808
+ const namespace = session.namespace;
6809
+ if (namespace !== void 0 && typeof namespace !== "string") {
6810
+ throw new Error("delegate session namespace metadata must be a string");
6811
+ }
6812
+ return { namespace: typeof namespace === "string" ? namespace.trim() || void 0 : void 0 };
6813
+ }
6814
+ return void 0;
6815
+ }
6816
+ async function rememberedNamespacesFor(sessionKey, namespaceBindings) {
6817
+ return namespaceBindings.namespacesFor(sessionKey);
6818
+ }
6819
+ async function rememberNamespace(sessionKey, namespace, namespaceBindings) {
6820
+ if (namespace.length > SESSION_NAMESPACE_BINDING_MAX_NAMESPACE_LENGTH) {
6821
+ throw new Error(
6822
+ `delegate session namespace exceeds the daemon limit of ${SESSION_NAMESPACE_BINDING_MAX_NAMESPACE_LENGTH} characters`
6823
+ );
6824
+ }
6825
+ try {
6826
+ await namespaceBindings.remember(sessionKey, namespace);
6827
+ } catch (err) {
6828
+ log4.warn(`delegate namespace binding persistence failed: ${String(err)}`);
6829
+ throw err;
6830
+ }
6831
+ }
6832
+ async function sessionNamespaceFrom(sessionKey, event, ctx, fallback, namespaceBindings) {
6833
+ const explicit = explicitSessionNamespaceFrom(sessionKey, event, ctx);
6834
+ if (explicit !== void 0) {
6835
+ await rememberNamespace(sessionKey, explicit.namespace ?? "", namespaceBindings);
6836
+ return explicit.namespace;
6837
+ }
6838
+ const remembered = await rememberedNamespacesFor(sessionKey, namespaceBindings);
6839
+ return remembered.length > 0 ? remembered.at(-1) || void 0 : fallback.trim() || void 0;
6840
+ }
6841
+ async function lifecycleSessionNamespacesFrom(sessionKey, event, ctx, fallback, namespaceBindings) {
6842
+ const explicit = explicitSessionNamespaceFrom(sessionKey, event, ctx);
6843
+ if (explicit !== void 0) {
6844
+ await rememberNamespace(sessionKey, explicit.namespace ?? "", namespaceBindings);
6845
+ }
6846
+ const remembered = await rememberedNamespacesFor(sessionKey, namespaceBindings);
6847
+ if (explicit !== void 0) {
6848
+ const explicitNamespace = explicit.namespace ?? "";
6849
+ const namespaces = remembered.includes(explicitNamespace) ? remembered : [...remembered, explicitNamespace];
6850
+ return namespaces.map((namespace) => namespace || void 0);
6851
+ }
6852
+ if (remembered.length > 0) return remembered.map((namespace) => namespace || void 0);
6853
+ return [fallback.trim() || void 0];
6854
+ }
6855
+
6856
+ // src/delegate-daemon-target.ts
6857
+ function daemonTargetFor(bridge) {
6858
+ const invalidatedCredentials = /* @__PURE__ */ new Set();
6859
+ const invalidated = (token) => invalidatedCredentials.has(`daemon configuration\0${token}`);
6860
+ return {
6861
+ host: bridge.daemonHost,
6862
+ port: bridge.daemonPort,
6863
+ invalidateAuthToken: (auth) => {
6864
+ invalidatedCredentials.add(`${auth.source}\0${auth.token}`);
6865
+ },
6866
+ resolveAuthToken: () => {
6867
+ if (bridge.daemonAuthTokenOverride !== void 0 && !invalidated(bridge.daemonAuthTokenOverride)) {
6868
+ const unit = bridge.daemonAuthUnit === void 0 ? void 0 : readUnitAuthToken(bridge.daemonAuthUnit);
6869
+ if (unit?.readable === true && unit.token !== void 0 && !invalidated(unit.token)) {
6870
+ return { token: unit.token, source: "daemon configuration" };
6871
+ }
6872
+ if ((unit === void 0 || unit.readable === false) && !invalidated(bridge.daemonAuthTokenOverride)) {
6873
+ return { token: bridge.daemonAuthTokenOverride, source: "daemon configuration" };
6874
+ }
6875
+ if (bridge.daemonConfigPath !== void 0) {
6876
+ const configToken = readDaemonConfigAuthToken(bridge.daemonConfigPath);
6877
+ if (configToken !== void 0 && !invalidated(configToken)) {
6878
+ return { token: configToken, source: "daemon configuration" };
6879
+ }
6880
+ }
6881
+ }
6882
+ if (bridge.daemonAuthPrefersConfig && bridge.daemonConfigPath !== void 0) {
6883
+ const configToken = readDaemonConfigAuthToken(bridge.daemonConfigPath);
6884
+ if (configToken !== void 0 && !invalidated(configToken)) {
6885
+ return { token: configToken, source: "daemon configuration" };
6886
+ }
6714
6887
  }
6715
6888
  return loadDaemonAuth(bridge.daemonConfigPath, invalidatedCredentials);
6716
6889
  }
@@ -6720,14 +6893,14 @@ function daemonTargetFor(bridge) {
6720
6893
  // src/delegate-flush-plan-ingest.ts
6721
6894
  import { constants as constants2 } from "fs";
6722
6895
  import { lstat as lstat4, open as open2, rename as rename2, unlink as unlink2, writeFile as writeFile2 } from "fs/promises";
6723
- import path8 from "path";
6724
- import { log as log4 } from "@remnic/core/logger";
6896
+ import path9 from "path";
6897
+ import { log as log5 } from "@remnic/core/logger";
6725
6898
  import { withHeldFileLock } from "@remnic/core/utils/serialize-mutations";
6726
6899
 
6727
6900
  // src/delegate-flush-plan-directory.ts
6728
6901
  import { constants } from "fs";
6729
6902
  import { lstat as lstat3, open, stat as stat2 } from "fs/promises";
6730
- import path7 from "path";
6903
+ import path8 from "path";
6731
6904
  function buildSnapshotPaths(planPath) {
6732
6905
  return {
6733
6906
  plan: planPath,
@@ -6746,7 +6919,7 @@ function descriptorDirectoryRoot(platform = process.platform) {
6746
6919
  async function pinSnapshotDirectory(paths, options = {}) {
6747
6920
  const descriptorRoot = options.descriptorRoot ?? descriptorDirectoryRoot();
6748
6921
  if (descriptorRoot === void 0) return { kind: "unsupported" };
6749
- const directory = path7.dirname(paths.plan);
6922
+ const directory = path8.dirname(paths.plan);
6750
6923
  let before;
6751
6924
  try {
6752
6925
  before = await lstat3(directory);
@@ -6767,7 +6940,7 @@ async function pinSnapshotDirectory(paths, options = {}) {
6767
6940
  await closeQuietly(handle);
6768
6941
  return { kind: "unstable" };
6769
6942
  }
6770
- const pinnedDirectory = path7.join(descriptorRoot, String(handle.fd));
6943
+ const pinnedDirectory = path8.join(descriptorRoot, String(handle.fd));
6771
6944
  let anchored;
6772
6945
  try {
6773
6946
  anchored = await stat2(pinnedDirectory);
@@ -6796,7 +6969,7 @@ async function pinSnapshotDirectory(paths, options = {}) {
6796
6969
  }
6797
6970
  }
6798
6971
  function anchoredChild(pinnedDirectory, realPath) {
6799
- return path7.join(pinnedDirectory, path7.basename(realPath));
6972
+ return path8.join(pinnedDirectory, path8.basename(realPath));
6800
6973
  }
6801
6974
  function isSameInode(left, right) {
6802
6975
  return left.dev === right.dev && left.ino === right.ino;
@@ -6890,14 +7063,14 @@ var MAX_RECLAIM_PASSES = 4;
6890
7063
  async function ingestFlushPlanNotes(options) {
6891
7064
  if (options.workspaceDir === void 0) return;
6892
7065
  const workspaceDir = options.workspaceDir;
6893
- const planPath = path8.join(
7066
+ const planPath = path9.join(
6894
7067
  workspaceDir,
6895
7068
  ...buildMemoryFlushPlan({ serviceId: options.serviceId }).relativePath.split("/")
6896
7069
  );
6897
7070
  const paths = buildSnapshotPaths(planPath);
6898
7071
  for (const candidate of [paths.plan, paths.inflight, paths.rotating, paths.oversized]) {
6899
7072
  if (await isLinkFreeUnder(workspaceDir, candidate)) continue;
6900
- log4.warn(
7073
+ log5.warn(
6901
7074
  `[${options.serviceId}] flush-plan ingestion skipped: ${candidate}, a parent, or the workspace root is a symlink`
6902
7075
  );
6903
7076
  return;
@@ -6908,21 +7081,21 @@ async function ingestFlushPlanNotes(options) {
6908
7081
  { staleMs: LOCK_STALE_MS, maxWaitMs: lockWaitMs },
6909
7082
  async (acquired, lock) => {
6910
7083
  if (!acquired) {
6911
- log4.warn(
7084
+ log5.warn(
6912
7085
  `[${options.serviceId}] flush-plan ingestion skipped: another flush holds the lock; the notes drain on the next flush`
6913
7086
  );
6914
7087
  return;
6915
7088
  }
6916
7089
  for (const candidate of [paths.plan, paths.inflight, paths.rotating, paths.oversized]) {
6917
7090
  if (await isLinkFreeUnder(workspaceDir, candidate)) continue;
6918
- log4.warn(
7091
+ log5.warn(
6919
7092
  `[${options.serviceId}] flush-plan ingestion skipped: ${candidate}, a parent, or the workspace root became a symlink`
6920
7093
  );
6921
7094
  return;
6922
7095
  }
6923
7096
  const pinned = await pinSnapshotDirectory(paths);
6924
7097
  if (pinned.kind === "unstable") {
6925
- log4.warn(
7098
+ log5.warn(
6926
7099
  `[${options.serviceId}] flush-plan ingestion skipped: the snapshot directory changed identity while it was being opened`
6927
7100
  );
6928
7101
  return;
@@ -6941,7 +7114,7 @@ async function ingestUnderLock(options, paths, lock) {
6941
7114
  if (options.remainingTimeoutMs() <= 0) return;
6942
7115
  const claimed = await claimPendingNotes(paths, options.serviceId, lock);
6943
7116
  if (claimed === void 0) {
6944
- log4.warn(
7117
+ log5.warn(
6945
7118
  `[${options.serviceId}] flush-plan lock was lost during recovery; its new owner drains the snapshot`
6946
7119
  );
6947
7120
  return;
@@ -6957,7 +7130,7 @@ async function ingestUnderLock(options, paths, lock) {
6957
7130
  while (pending.trim().length > 0) {
6958
7131
  const timeoutMs = options.remainingTimeoutMs();
6959
7132
  if (timeoutMs <= 0) {
6960
- log4.warn(
7133
+ log5.warn(
6961
7134
  `[${options.serviceId}] flush-plan ingestion stopped: the caller's deadline is spent; the remainder drains on the next flush`
6962
7135
  );
6963
7136
  stop = true;
@@ -6980,7 +7153,7 @@ async function ingestUnderLock(options, paths, lock) {
6980
7153
  timeoutMs
6981
7154
  );
6982
7155
  if (isRefusal(response.status)) {
6983
- log4.warn(
7156
+ log5.warn(
6984
7157
  `[${options.serviceId}] flush-plan notes were refused by the daemon (${response.status}); keeping them for the next flush`
6985
7158
  );
6986
7159
  stop = true;
@@ -6994,7 +7167,7 @@ async function ingestUnderLock(options, paths, lock) {
6994
7167
  pending = await quarantineOversizedLine(paths, pending, options.serviceId);
6995
7168
  chunkBytes = MAX_OBSERVE_CHUNK_BYTES;
6996
7169
  if (!await lock.refresh()) {
6997
- log4.warn(
7170
+ log5.warn(
6998
7171
  `[${options.serviceId}] flush-plan lock was lost mid-flush; the snapshot is left to its new owner`
6999
7172
  );
7000
7173
  lockLost = true;
@@ -7010,7 +7183,7 @@ async function ingestUnderLock(options, paths, lock) {
7010
7183
  }
7011
7184
  pending = pending.slice(chunk.length);
7012
7185
  if (!await lock.refresh()) {
7013
- log4.warn(
7186
+ log5.warn(
7014
7187
  `[${options.serviceId}] flush-plan lock was lost mid-flush; the snapshot is left to its new owner`
7015
7188
  );
7016
7189
  lockLost = true;
@@ -7023,7 +7196,7 @@ async function ingestUnderLock(options, paths, lock) {
7023
7196
  if (!lockLost && await lock.refresh()) {
7024
7197
  await releasePendingNotes(paths, pending, options.serviceId);
7025
7198
  } else if (!lockLost) {
7026
- log4.warn(
7199
+ log5.warn(
7027
7200
  `[${options.serviceId}] flush-plan lock was lost before the snapshot could be updated; its new owner drains the remainder`
7028
7201
  );
7029
7202
  }
@@ -7043,7 +7216,7 @@ async function claimPendingNotes(paths, serviceId, lock) {
7043
7216
  const recovery = await mergeRotatedIntoInflight(paths, lock);
7044
7217
  if (recovery === "lock-lost") return void 0;
7045
7218
  if (recovery === "recovered") {
7046
- log4.warn(`[${serviceId}] recovered flush-plan notes left by an interrupted ingestion`);
7219
+ log5.warn(`[${serviceId}] recovered flush-plan notes left by an interrupted ingestion`);
7047
7220
  }
7048
7221
  if (!await lock.refresh()) return void 0;
7049
7222
  try {
@@ -7082,7 +7255,7 @@ async function releasePendingNotes(paths, pending, serviceId) {
7082
7255
  }
7083
7256
  await atomicWrite(paths.inflight, pending);
7084
7257
  } catch (err) {
7085
- log4.warn(
7258
+ log5.warn(
7086
7259
  `[${serviceId}] could not persist unsent flush-plan notes; the next ingestion recovers them: ${String(err)}`
7087
7260
  );
7088
7261
  }
@@ -7102,7 +7275,7 @@ async function quarantineOversizedLine(paths, pending, serviceId) {
7102
7275
  } finally {
7103
7276
  await handle.close();
7104
7277
  }
7105
- log4.warn(
7278
+ log5.warn(
7106
7279
  `[${serviceId}] a flush-plan note exceeds the daemon's body limit; moved it to ${paths.oversizedLabel} so the remaining notes can drain`
7107
7280
  );
7108
7281
  return rest;
@@ -7151,16 +7324,16 @@ function chunkOnLineBoundaries(text, limit) {
7151
7324
  return chunks;
7152
7325
  }
7153
7326
  async function isLinkFreeUnder(root, target) {
7154
- const relative = path8.relative(root, target);
7155
- if (relative.startsWith("..") || path8.isAbsolute(relative)) return false;
7327
+ const relative = path9.relative(root, target);
7328
+ if (relative.startsWith("..") || path9.isAbsolute(relative)) return false;
7156
7329
  let current = root;
7157
7330
  try {
7158
7331
  if ((await lstat4(current)).isSymbolicLink()) return false;
7159
7332
  } catch {
7160
7333
  return true;
7161
7334
  }
7162
- for (const segment of relative.split(path8.sep)) {
7163
- current = path8.join(current, segment);
7335
+ for (const segment of relative.split(path9.sep)) {
7336
+ current = path9.join(current, segment);
7164
7337
  try {
7165
7338
  if ((await lstat4(current)).isSymbolicLink()) return false;
7166
7339
  } catch {
@@ -7196,7 +7369,7 @@ function extractTextContent(msg) {
7196
7369
 
7197
7370
  // src/delegate-capability.ts
7198
7371
  import { readFile as readFile2 } from "fs/promises";
7199
- import { log as log5 } from "@remnic/core/logger";
7372
+ import { log as log6 } from "@remnic/core/logger";
7200
7373
  var HEALTH_CACHE_TTL_MS = 3e4;
7201
7374
  var SEARCH_CANDIDATE_CAP = 25e3;
7202
7375
  function searchCandidateCeiling(budget) {
@@ -7393,7 +7566,7 @@ function createDelegateMemoryCapability(options) {
7393
7566
  corpusShared = daemonIsLocal && health.memoryDir !== void 0 && daemonServesCorpus(options.memoryDir, health.memoryDir);
7394
7567
  if (!corpusShared && daemonIsLocal && !reportedCorpusMismatch) {
7395
7568
  reportedCorpusMismatch = true;
7396
- log5.error(
7569
+ log6.error(
7397
7570
  `[${serviceId}] delegate capability: the daemon does not serve this plugin's memoryDir (daemon: ${health.memoryDir ?? "unreported"}, plugin: ${options.memoryDir}) \u2014 file-backed reads and public artifacts are disabled; search still runs through the daemon`
7398
7571
  );
7399
7572
  }
@@ -7411,7 +7584,7 @@ function createDelegateMemoryCapability(options) {
7411
7584
  const message = `[${serviceId}] delegate capability health probe failed: ${String(err)}`;
7412
7585
  if (message !== lastHealthFailure) {
7413
7586
  lastHealthFailure = message;
7414
- log5.warn(message);
7587
+ log6.warn(message);
7415
7588
  }
7416
7589
  } finally {
7417
7590
  healthInFlight = void 0;
@@ -7638,7 +7811,7 @@ function createDelegateMemoryCapability(options) {
7638
7811
  agentIds: options.agentIds
7639
7812
  });
7640
7813
  } catch (err) {
7641
- log5.error(`[${serviceId}] delegate publicArtifacts.listArtifacts failed`, err);
7814
+ log6.error(`[${serviceId}] delegate publicArtifacts.listArtifacts failed`, err);
7642
7815
  return [];
7643
7816
  }
7644
7817
  },
@@ -7651,7 +7824,7 @@ function registerDelegateMemoryCapability(api, options) {
7651
7824
  const hasRuntime = typeof api.registerMemoryRuntime === "function";
7652
7825
  const hasFlushPlan = typeof api.registerMemoryFlushPlan === "function";
7653
7826
  if (!hasUnified && !hasRuntime && !hasFlushPlan) {
7654
- log5.debug(
7827
+ log6.debug(
7655
7828
  `[${options.serviceId}] delegate: host exposes no memory capability surface \u2014 nothing to register`
7656
7829
  );
7657
7830
  return built;
@@ -7668,7 +7841,7 @@ function registerDelegateMemoryCapability(api, options) {
7668
7841
  if (hasFlushPlan) api.registerMemoryFlushPlan?.(built.flushPlanResolver);
7669
7842
  const surface = hasUnified ? "memory capability with publicArtifacts provider" : "split memory runtime/flush-plan surfaces";
7670
7843
  const builder = options.allowPromptInjection ? " and promptBuilder" : " (promptBuilder omitted \u2014 injection disabled by policy)";
7671
- log5.info(`[${options.serviceId}] delegate: registered daemon-backed ${surface}${builder}`);
7844
+ log6.info(`[${options.serviceId}] delegate: registered daemon-backed ${surface}${builder}`);
7672
7845
  return built;
7673
7846
  }
7674
7847
 
@@ -7680,7 +7853,7 @@ import {
7680
7853
  acceptsSupportPassportModelResponse,
7681
7854
  parseSupportPassportModelJob
7682
7855
  } from "@remnic/core";
7683
- import { log as log6 } from "@remnic/core/logger";
7856
+ import { log as log7 } from "@remnic/core/logger";
7684
7857
  var MODEL_WORKER_COUNT = 4;
7685
7858
  var DEFAULT_REQUEST_TIMEOUT_MS = 25e3;
7686
7859
  var RESULT_REQUEST_TIMEOUT_MS = 5e3;
@@ -7753,7 +7926,7 @@ function createDelegateSupportPassportModelService(options) {
7753
7926
  ]);
7754
7927
  } catch (error) {
7755
7928
  if (!modelSignal.aborted) {
7756
- log6.warn(`delegate support passport model call failed: ${String(error)}`);
7929
+ log7.warn(`delegate support passport model call failed: ${String(error)}`);
7757
7930
  }
7758
7931
  return null;
7759
7932
  } finally {
@@ -7898,14 +8071,14 @@ function createDelegateSupportPassportModelService(options) {
7898
8071
  }
7899
8072
  const job = parseSupportPassportModelJob(await response.json());
7900
8073
  if (!job) {
7901
- log6.warn("delegate support passport model bridge received an invalid job");
8074
+ log7.warn("delegate support passport model bridge received an invalid job");
7902
8075
  await delayAfterFailure();
7903
8076
  continue;
7904
8077
  }
7905
8078
  consecutiveFailures = 0;
7906
8079
  const deadline = Date.now() + job.timeoutMs;
7907
8080
  if (!await acknowledge(job, signal, job.claimAckTimeoutMs ?? job.timeoutMs, true)) {
7908
- log6.warn("delegate support passport model bridge could not acknowledge a claimed job");
8081
+ log7.warn("delegate support passport model bridge could not acknowledge a claimed job");
7909
8082
  await delayAfterFailure();
7910
8083
  continue;
7911
8084
  }
@@ -7931,7 +8104,7 @@ function createDelegateSupportPassportModelService(options) {
7931
8104
  });
7932
8105
  if (heartbeatError && !completionAccepted) throw heartbeatError;
7933
8106
  } catch (error) {
7934
- log6.warn(`delegate support passport model completion failed: ${String(error)}`);
8107
+ log7.warn(`delegate support passport model completion failed: ${String(error)}`);
7935
8108
  } finally {
7936
8109
  heartbeatController.abort();
7937
8110
  workController.abort();
@@ -7939,7 +8112,7 @@ function createDelegateSupportPassportModelService(options) {
7939
8112
  }
7940
8113
  } catch (error) {
7941
8114
  if (signal.aborted) break;
7942
- log6.warn(`delegate support passport model bridge failed: ${String(error)}`);
8115
+ log7.warn(`delegate support passport model bridge failed: ${String(error)}`);
7943
8116
  await delayAfterFailure();
7944
8117
  }
7945
8118
  }
@@ -7964,9 +8137,228 @@ function createDelegateSupportPassportModelService(options) {
7964
8137
  };
7965
8138
  }
7966
8139
 
7967
- // src/delegate-runtime.ts
7968
- var DELEGATE_BATCH_FLUSH_CACHE_TTL_MS = 3e4;
7969
- var delegatePassportServiceApiServices = /* @__PURE__ */ new WeakMap();
8140
+ // src/delegate-tools.ts
8141
+ import path10 from "path";
8142
+ import {
8143
+ collapseWhitespace,
8144
+ truncateCodePointSafe
8145
+ } from "@remnic/core";
8146
+ import { isHandleToken } from "@remnic/core/recall-handles";
8147
+ var DEFAULT_SEARCH_RESULTS = 8;
8148
+ var DEFAULT_SNIPPET_MAX_CHARS = 600;
8149
+ var DAEMON_SEARCH_QUERY_MAX_CHARS = 2048;
8150
+ var DAEMON_MEMORY_GET_MAX_CHARS = 512;
8151
+ function clampLimit(value) {
8152
+ if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_SEARCH_RESULTS;
8153
+ return Math.max(1, Math.min(50, Math.floor(value)));
8154
+ }
8155
+ function clampSnippetMaxChars(value) {
8156
+ if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_SNIPPET_MAX_CHARS;
8157
+ return Math.max(1, Math.min(4e3, Math.floor(value)));
8158
+ }
8159
+ function sessionKeyFor(params, ctx) {
8160
+ if (typeof ctx?.sessionKey === "string" && ctx.sessionKey.trim().length > 0) {
8161
+ return ctx.sessionKey;
8162
+ }
8163
+ return typeof params.sessionKey === "string" && params.sessionKey.trim().length > 0 ? params.sessionKey : "default";
8164
+ }
8165
+ function activeMemoryGetOutputFrom(record) {
8166
+ if (typeof record !== "object" || record === null) {
8167
+ throw new Error("daemon memory route responded 2xx without a memory record");
8168
+ }
8169
+ const memory = record;
8170
+ if (typeof memory.id !== "string" || typeof memory.content !== "string") {
8171
+ throw new Error("daemon memory route returned a malformed memory record");
8172
+ }
8173
+ const frontmatter = typeof memory.frontmatter === "object" && memory.frontmatter !== null ? memory.frontmatter : {};
8174
+ const metadata = {};
8175
+ if (frontmatter.category === "fact" || frontmatter.category === "preference") {
8176
+ metadata.type = frontmatter.category;
8177
+ }
8178
+ if (Array.isArray(frontmatter.tags) && typeof frontmatter.tags[0] === "string") {
8179
+ metadata.topic = frontmatter.tags[0];
8180
+ }
8181
+ if (typeof frontmatter.updated === "string") metadata.updatedAt = frontmatter.updated;
8182
+ if (typeof frontmatter.source === "string") metadata.sourceUri = frontmatter.source;
8183
+ return {
8184
+ id: memory.id,
8185
+ text: collapseWhitespace(memory.content),
8186
+ ...Object.keys(metadata).length > 0 ? { metadata } : {}
8187
+ };
8188
+ }
8189
+ function buildDelegateMemorySearchTool(options) {
8190
+ const spent = (deadline, signal, stage) => {
8191
+ if (signal?.aborted) throw new Error(`memory_search aborted before ${stage}`);
8192
+ if (deadline - Date.now() <= 0) throw new Error(`memory_search budget of ${options.timeoutMs}ms spent before ${stage}`);
8193
+ };
8194
+ return {
8195
+ name: "memory_search",
8196
+ description: "Search Remnic memories via the Remnic daemon (delegate).",
8197
+ parameters: MemorySearchInputSchema,
8198
+ inputSchema: MemorySearchInputSchema,
8199
+ async execute(_toolCallId, params, signal, ctx) {
8200
+ const query = typeof params.query === "string" ? params.query.trim() : "";
8201
+ if (query.length === 0) throw new Error("memory_search requires a non-empty query");
8202
+ const deadline = Date.now() + options.timeoutMs;
8203
+ const { manager, error } = await options.runtime.getMemorySearchManager({
8204
+ cfg: void 0,
8205
+ agentId: ctx?.agentId ?? options.agentId
8206
+ });
8207
+ if (!manager) throw new Error(error ?? "delegate memory search manager unavailable");
8208
+ spent(deadline, signal, "scope resolution");
8209
+ const limit = clampLimit(params.limit);
8210
+ const sessionKey = sessionKeyFor(params, ctx);
8211
+ const filters = params.filters && typeof params.filters === "object" ? params.filters : void 0;
8212
+ if (typeof filters?.namespace === "string" && filters.namespace.trim().length > 0) {
8213
+ throw new Error(
8214
+ `memory_search filters.namespace "${filters.namespace.trim()}" is not supported in delegate mode: the search is scoped to the session's own namespace`
8215
+ );
8216
+ }
8217
+ spent(deadline, signal, "search");
8218
+ const results = await manager.search(query.slice(0, DAEMON_SEARCH_QUERY_MAX_CHARS), {
8219
+ maxResults: limit + 1,
8220
+ sessionKey
8221
+ });
8222
+ const output = {
8223
+ results: results.slice(0, limit).map((result) => ({
8224
+ id: path10.basename(result.citation ?? result.path, ".md"),
8225
+ score: result.score,
8226
+ // Read per call: the owner's cap can change on an active takeover.
8227
+ text: truncateCodePointSafe(collapseWhitespace(result.snippet), clampSnippetMaxChars(options.snippetMaxChars))
8228
+ })),
8229
+ truncated: results.length > limit
8230
+ };
8231
+ return toolJsonResult2(output);
8232
+ }
8233
+ };
8234
+ }
8235
+ function buildDelegateMemoryGetTool(options) {
8236
+ return {
8237
+ name: "memory_get",
8238
+ description: "Fetch one Remnic memory via the Remnic daemon (delegate).",
8239
+ parameters: MemoryGetInputSchema,
8240
+ inputSchema: MemoryGetInputSchema,
8241
+ async execute(_toolCallId, params, _signal, ctx) {
8242
+ const id = typeof params.id === "string" ? params.id.trim() : "";
8243
+ if (id.length === 0) throw new Error("memory_get requires an id");
8244
+ if (id.length > DAEMON_MEMORY_GET_MAX_CHARS) {
8245
+ return toolJsonResult2({ error: "not_found" });
8246
+ }
8247
+ const sessionKey = sessionKeyFor(params, ctx);
8248
+ if (sessionKey.length > DAEMON_MEMORY_GET_MAX_CHARS) {
8249
+ throw new Error(`memory_get sessionKey exceeds the daemon's ${DAEMON_MEMORY_GET_MAX_CHARS}-char cap`);
8250
+ }
8251
+ const deadline = Date.now() + options.timeoutMs;
8252
+ const namespace = await options.resolveNamespace(sessionKey, Math.max(1, deadline - Date.now()));
8253
+ const requested = typeof params.namespace === "string" && params.namespace.trim().length > 0 ? params.namespace.trim() : void 0;
8254
+ if (requested !== void 0 && requested !== namespace) {
8255
+ throw new Error(
8256
+ `memory_get namespace "${requested}" does not match the session's memory scope${namespace === void 0 ? "" : ` "${namespace}"`}`
8257
+ );
8258
+ }
8259
+ const search = new URLSearchParams({ sessionKey });
8260
+ if (namespace !== void 0) search.set("namespace", namespace);
8261
+ const pathname = `/engram/v1/memories/${encodeURIComponent(id)}?${search}`;
8262
+ const response = await getJson(options.target, options.serviceId, pathname, Math.max(1, deadline - Date.now()));
8263
+ if (response.status === 404) return toolJsonResult2({ error: "not_found" });
8264
+ if (response.status === 400 && isHandleToken(id)) {
8265
+ return toolJsonResult2({ error: "not_found" });
8266
+ }
8267
+ if (response.status < 200 || response.status > 299) {
8268
+ throw new Error(`daemon ${pathname} responded ${response.status}`);
8269
+ }
8270
+ return toolJsonResult2(activeMemoryGetOutputFrom(response.body?.memory));
8271
+ }
8272
+ };
8273
+ }
8274
+ var SHARED_TOOL_SHAPES = {
8275
+ memory_search: {
8276
+ description: "Search Remnic memories for the OpenClaw active-memory surface.",
8277
+ parameters: MemorySearchInputSchema
8278
+ },
8279
+ memory_get: {
8280
+ description: "Fetch one Remnic memory for the OpenClaw active-memory surface.",
8281
+ parameters: MemoryGetInputSchema
8282
+ }
8283
+ };
8284
+ function installSharedTools(registerTool, owner, tools) {
8285
+ for (const tool of tools) owner.serve[tool.name] = tool;
8286
+ for (const tool of tools) {
8287
+ if (owner.installed.has(tool.name)) continue;
8288
+ owner.installed.add(tool.name);
8289
+ const shape = SHARED_TOOL_SHAPES[tool.name];
8290
+ registerTool(
8291
+ {
8292
+ ...tool,
8293
+ ...shape === void 0 ? {} : { description: shape.description, parameters: shape.parameters, inputSchema: shape.parameters },
8294
+ execute: async (...args) => {
8295
+ const serving = owner.serve[tool.name] ?? tool;
8296
+ if (!owner.enabled && tool.name !== "memory_search") {
8297
+ throw new Error(`${tool.name} is disabled: the memory slot owner set openclawToolsEnabled: false`);
8298
+ }
8299
+ return serving.execute(...args);
8300
+ }
8301
+ },
8302
+ { name: tool.name }
8303
+ );
8304
+ }
8305
+ }
8306
+ var delegateToolOwners = /* @__PURE__ */ new WeakMap();
8307
+ function registerDelegateTools(api, options) {
8308
+ if (typeof api.registerTool !== "function") return;
8309
+ const registerTool = api.registerTool.bind(api);
8310
+ const existing = delegateToolOwners.get(api);
8311
+ const owner = existing ?? {
8312
+ enabled: options.enabled,
8313
+ passive: options.passive,
8314
+ installed: /* @__PURE__ */ new Set(),
8315
+ serve: {}
8316
+ };
8317
+ if (existing === void 0) delegateToolOwners.set(api, owner);
8318
+ else if (existing.passive && !options.passive) {
8319
+ owner.enabled = options.enabled;
8320
+ owner.passive = options.passive;
8321
+ } else return;
8322
+ const resolveNamespace = (operation) => async (sessionKey, timeoutMs) => {
8323
+ const deadline = Date.now() + timeoutMs;
8324
+ const bound = await options.resolveSearchNamespace(sessionKey);
8325
+ return options.resolveScopedNamespace(bound, Math.max(1, deadline - Date.now()), [operation]);
8326
+ };
8327
+ const tools = [buildDelegateMemorySearchTool(options)];
8328
+ if (owner.enabled) {
8329
+ tools.push(
8330
+ buildDelegateMemoryGetTool({
8331
+ target: options.target,
8332
+ serviceId: options.serviceId,
8333
+ timeoutMs: options.timeoutMs,
8334
+ resolveNamespace: resolveNamespace("memory_get")
8335
+ })
8336
+ );
8337
+ }
8338
+ installSharedTools(registerTool, owner, tools);
8339
+ }
8340
+ function registerEmbeddedTools(api, options) {
8341
+ if (typeof api.registerTool !== "function") return [];
8342
+ const registerTool = api.registerTool.bind(api);
8343
+ const existing = delegateToolOwners.get(api);
8344
+ const owner = existing ?? {
8345
+ enabled: options.enabled,
8346
+ passive: options.passive,
8347
+ installed: /* @__PURE__ */ new Set(),
8348
+ serve: {}
8349
+ };
8350
+ if (existing === void 0) delegateToolOwners.set(api, owner);
8351
+ else if (options.passive && !existing.passive) return [...owner.installed];
8352
+ else if (!options.passive) {
8353
+ owner.enabled = options.enabled;
8354
+ owner.passive = false;
8355
+ }
8356
+ installSharedTools(registerTool, owner, options.tools);
8357
+ return [...owner.installed];
8358
+ }
8359
+
8360
+ // src/delegate-hook-fields.ts
8361
+ var MAX_RECALL_QUERY_CHARS = 1500;
7970
8362
  function sessionKeyFrom(event, ctx) {
7971
8363
  const fromCtx = ctx?.sessionKey;
7972
8364
  if (typeof fromCtx === "string" && fromCtx.length > 0) return fromCtx;
@@ -7981,13 +8373,13 @@ function lifecycleSessionKeyFrom(event, ctx) {
7981
8373
  }
7982
8374
  return sessionKeyFrom(event, ctx);
7983
8375
  }
7984
- function recallQueryFrom(event) {
7985
- let prompt = typeof event.prompt === "string" ? event.prompt : void 0;
7986
- if ((!prompt || prompt.length < 5) && Array.isArray(event.messages)) {
8376
+ function recallQueryFrom(event, cleanUserMessage) {
8377
+ let prompt = typeof event.prompt === "string" ? cleanUserMessage(event.prompt).trim() : "";
8378
+ if (prompt.length < 5 && Array.isArray(event.messages)) {
7987
8379
  const msgs = event.messages;
7988
8380
  for (let i = msgs.length - 1; i >= 0; i--) {
7989
8381
  if (msgs[i]?.role === "user") {
7990
- const text = extractTextContent(msgs[i]);
8382
+ const text = extractTextContent(msgs[i]).trim();
7991
8383
  if (text.length >= 5) {
7992
8384
  prompt = text;
7993
8385
  break;
@@ -7995,7 +8387,7 @@ function recallQueryFrom(event) {
7995
8387
  }
7996
8388
  }
7997
8389
  }
7998
- return prompt ?? "";
8390
+ return prompt.length > MAX_RECALL_QUERY_CHARS ? prompt.slice(-MAX_RECALL_QUERY_CHARS) : prompt;
7999
8391
  }
8000
8392
  function cwdFrom(event, ctx, fallback) {
8001
8393
  const runtime = ctx?.runtime;
@@ -8025,17 +8417,21 @@ function readContextComposition(response, fallbackContext) {
8025
8417
  if (degradation) composition.degradation = degradation;
8026
8418
  return composition;
8027
8419
  }
8420
+
8421
+ // src/delegate-runtime.ts
8422
+ var DELEGATE_BATCH_FLUSH_CACHE_TTL_MS = 3e4;
8423
+ var delegatePassportServiceApiServices = /* @__PURE__ */ new WeakMap();
8028
8424
  function registerDelegateRuntime(api, options) {
8029
8425
  const { target, namespace, namespaceBindings } = options;
8030
8426
  const now = options.now ?? Date.now;
8031
8427
  if (options.supportPassportModelRoute) {
8032
8428
  const registeredServices = delegatePassportServiceApiServices.get(api);
8033
8429
  if (registeredServices?.has(options.serviceId)) {
8034
- log7.debug(
8430
+ log8.debug(
8035
8431
  `delegate register: ${options.serviceId} already has its support passport model service on this api`
8036
8432
  );
8037
8433
  } else if (typeof api.registerService !== "function") {
8038
- log7.error(
8434
+ log8.error(
8039
8435
  `[${options.serviceId}] delegate support passport gateway routing is unavailable: host exposes no service registration surface`
8040
8436
  );
8041
8437
  } else {
@@ -8053,32 +8449,22 @@ function registerDelegateRuntime(api, options) {
8053
8449
  }
8054
8450
  }
8055
8451
  }
8056
- if (options.passive) {
8057
- log7.info(
8058
- `[${options.serviceId}] bridge mode delegate: memory slot not owned \u2014 passive, no memory hooks registered`
8059
- );
8060
- return;
8061
- }
8062
8452
  const promptLinesBySession = /* @__PURE__ */ new Map();
8063
8453
  const promptInjectionEnabled = options.allowPromptInjection && options.recallBudgetChars !== 0;
8064
8454
  const useSectionBuilder = typeof api.registerMemoryPromptSection === "function";
8065
8455
  const cachePromptLines = useSectionBuilder;
8066
- const capability = registerDelegateMemoryCapability(api, {
8456
+ const resolveSearchNamespace = async (sessionKey) => {
8457
+ if (typeof sessionKey === "string" && sessionKey.trim().length > 0) {
8458
+ const remembered = await rememberedNamespacesFor(sessionKey, namespaceBindings);
8459
+ if (remembered.length > 0) return remembered.at(-1) || void 0;
8460
+ }
8461
+ return namespace.trim() || void 0;
8462
+ };
8463
+ const capabilityOptions = {
8067
8464
  serviceId: options.serviceId,
8068
8465
  target,
8069
8466
  namespace,
8070
- // Capability searches scope through the SAME per-session binding history
8071
- // the hooks use (the non-explicit branch of sessionNamespaceFrom): the
8072
- // host hands the runtime a sessionKey but no event/ctx to read an explicit
8073
- // namespace from, so the remembered binding — else the registration-wide
8074
- // fallback — is the correct scope.
8075
- resolveSearchNamespace: async (sessionKey) => {
8076
- if (typeof sessionKey === "string" && sessionKey.trim().length > 0) {
8077
- const remembered = await rememberedNamespacesFor(sessionKey, namespaceBindings);
8078
- if (remembered.length > 0) return remembered.at(-1) || void 0;
8079
- }
8080
- return namespace.trim() || void 0;
8081
- },
8467
+ resolveSearchNamespace,
8082
8468
  // The daemon's own namespace-aware probe, so a substituted default is
8083
8469
  // proven usable before the first search rather than 403-ing on it.
8084
8470
  verifyNamespaceAuthorization: async (candidate, timeoutMs, operations) => {
@@ -8107,24 +8493,44 @@ function registerDelegateRuntime(api, options) {
8107
8493
  searchTimeoutMs: options.recallTimeoutMs,
8108
8494
  healthTimeoutMs: options.recallTimeoutMs,
8109
8495
  now: options.now
8496
+ };
8497
+ const capability = options.passive ? createDelegateMemoryCapability(capabilityOptions) : registerDelegateMemoryCapability(api, capabilityOptions);
8498
+ registerDelegateTools(api, {
8499
+ target,
8500
+ serviceId: options.serviceId,
8501
+ enabled: options.openclawToolsEnabled !== false,
8502
+ passive: options.passive,
8503
+ runtime: capability.runtime,
8504
+ agentId: options.capability.agentIds[0] ?? "main",
8505
+ snippetMaxChars: options.openclawToolSnippetMaxChars,
8506
+ timeoutMs: options.recallTimeoutMs,
8507
+ resolveSearchNamespace,
8508
+ resolveScopedNamespace: capability.resolveScopedNamespace
8110
8509
  });
8510
+ if (options.passive) {
8511
+ log8.info(
8512
+ `[${options.serviceId}] bridge mode delegate: memory slot not owned \u2014 passive, no memory hooks registered`
8513
+ );
8514
+ return;
8515
+ }
8516
+ const observeChains = /* @__PURE__ */ new Map();
8517
+ const followUpFlushSessions = /* @__PURE__ */ new Map();
8111
8518
  if (promptInjectionEnabled) {
8112
- const recallHandler = async (event, ctx) => {
8113
- const query = recallQueryFrom(event);
8519
+ const recallContext = async (query, event, ctx) => {
8114
8520
  const sessionKey = sessionKeyFrom(event, ctx);
8115
8521
  if (cachePromptLines) promptLinesBySession.delete(sessionKey);
8116
- if (query.trim().length < 5) return void 0;
8522
+ if (query.length < 5) return void 0;
8117
8523
  const promptDeadline = Date.now() + Math.min(options.hookTimeoutMs, options.recallTimeoutMs);
8118
8524
  const promptRemaining = () => promptDeadline - Date.now();
8119
8525
  try {
8120
8526
  if (options.shouldSkipRecall(sessionKey)) {
8121
- log7.debug(`delegate recall skipped: cron policy excludes ${sessionKey}`);
8527
+ log8.debug(`delegate recall skipped: cron policy excludes ${sessionKey}`);
8122
8528
  return void 0;
8123
8529
  }
8124
8530
  const runtimeAgent = ctx?.runtime?.agent;
8125
8531
  const agentId = (typeof ctx?.agentId === "string" ? ctx.agentId : void 0) ?? (typeof runtimeAgent?.id === "string" ? runtimeAgent.id : void 0) ?? "main";
8126
8532
  if (await options.resolveSessionDisabled(sessionKey, agentId)) {
8127
- log7.debug(`delegate recall skipped: session toggle disables memory for ${sessionKey}`);
8533
+ log8.debug(`delegate recall skipped: session toggle disables memory for ${sessionKey}`);
8128
8534
  return void 0;
8129
8535
  }
8130
8536
  const cwd = cwdFrom(event, ctx, options.cwd);
@@ -8165,20 +8571,28 @@ function registerDelegateRuntime(api, options) {
8165
8571
  maxChars: options.recallBudgetChars
8166
8572
  });
8167
8573
  if (!rendered) return void 0;
8168
- const prompt = rendered.prompt;
8169
- if (cachePromptLines) {
8170
- promptLinesBySession.set(sessionKey, rendered.lines);
8171
- return void 0;
8172
- }
8173
8574
  return {
8174
- prependSystemContext: prompt,
8575
+ prompt: rendered.prompt,
8576
+ lines: rendered.lines,
8175
8577
  ...composition.degradation ? { degradation: composition.degradation } : {}
8176
8578
  };
8177
8579
  } catch (err) {
8178
- log7.warn(`delegate recall failed: ${String(err)}`);
8580
+ log8.warn(`delegate recall failed: ${String(err)}`);
8179
8581
  return void 0;
8180
8582
  }
8181
8583
  };
8584
+ const recallHandler = async (event, ctx) => {
8585
+ const recalled = await recallContext(recallQueryFrom(event, options.cleanUserMessage), event, ctx);
8586
+ if (!recalled) return void 0;
8587
+ if (cachePromptLines) {
8588
+ promptLinesBySession.set(sessionKeyFrom(event, ctx), recalled.lines);
8589
+ return void 0;
8590
+ }
8591
+ return {
8592
+ prependSystemContext: recalled.prompt,
8593
+ ...recalled.degradation ? { degradation: recalled.degradation } : {}
8594
+ };
8595
+ };
8182
8596
  api.on(
8183
8597
  "before_prompt_build",
8184
8598
  (event, ctx) => recallHandler(event, ctx),
@@ -8197,7 +8611,7 @@ function registerDelegateRuntime(api, options) {
8197
8611
  api.registerMemoryPromptSection(memoryBuildFn);
8198
8612
  }
8199
8613
  } else {
8200
- log7.info(
8614
+ log8.info(
8201
8615
  `[${options.serviceId}] bridge mode delegate: prompt injection disabled by hooks policy`
8202
8616
  );
8203
8617
  }
@@ -8215,36 +8629,49 @@ function registerDelegateRuntime(api, options) {
8215
8629
  (message) => (message.role === "user" || message.role === "assistant") && message.content.trim().length > 0
8216
8630
  );
8217
8631
  if (turn.length === 0) return;
8632
+ const cwd = cwdFrom(event, ctx, options.cwd);
8633
+ let scopedNamespace;
8218
8634
  try {
8219
- const observeDeadline = Date.now() + options.observeTimeoutMs;
8220
- const observeRemaining = () => observeDeadline - Date.now();
8221
- const cwd = cwdFrom(event, ctx, options.cwd);
8222
- const scopedNamespace = await sessionNamespaceFrom(
8635
+ scopedNamespace = await sessionNamespaceFrom(
8223
8636
  sessionKey,
8224
8637
  event,
8225
8638
  ctx,
8226
8639
  namespace,
8227
8640
  namespaceBindings
8228
8641
  );
8229
- await postJson(
8230
- target,
8231
- options.serviceId,
8232
- "/engram/v1/observe",
8233
- await withNamespace(
8234
- scopedNamespace,
8235
- {
8236
- sessionKey,
8237
- messages: turn,
8238
- ...cwd ? { cwd } : {},
8239
- ...options.projectTag ? { projectTag: options.projectTag } : {}
8240
- },
8241
- (explicit) => capability.resolveScopedNamespace(explicit, observeRemaining(), ["observe"])
8242
- ),
8243
- Math.max(1, observeRemaining())
8244
- );
8245
8642
  } catch (err) {
8246
- log7.warn(`delegate observe failed: ${String(err)}`);
8643
+ log8.warn(`delegate observe failed: ${String(err)}`);
8644
+ return;
8247
8645
  }
8646
+ const observe = async () => {
8647
+ const observeDeadline = Date.now() + Math.min(options.observeTimeoutMs, options.flushTimeoutMs / 2);
8648
+ const observeRemaining = () => observeDeadline - Date.now();
8649
+ try {
8650
+ await postJson(
8651
+ target,
8652
+ options.serviceId,
8653
+ "/engram/v1/observe",
8654
+ await withNamespace(
8655
+ scopedNamespace,
8656
+ {
8657
+ sessionKey,
8658
+ messages: turn,
8659
+ ...cwd ? { cwd } : {},
8660
+ ...options.projectTag ? { projectTag: options.projectTag } : {}
8661
+ },
8662
+ (explicit) => capability.resolveScopedNamespace(explicit, observeRemaining(), ["observe"])
8663
+ ),
8664
+ Math.max(1, observeRemaining())
8665
+ );
8666
+ } catch (err) {
8667
+ log8.warn(`delegate observe failed: ${String(err)}`);
8668
+ }
8669
+ };
8670
+ const chained = (observeChains.get(sessionKey) ?? Promise.resolve()).then(observe);
8671
+ observeChains.set(sessionKey, chained);
8672
+ void chained.then(() => {
8673
+ if (observeChains.get(sessionKey) === chained) observeChains.delete(sessionKey);
8674
+ });
8248
8675
  });
8249
8676
  let cachedBatchFlushSupport;
8250
8677
  let cachedBatchFlushSupportExpiresAt = 0;
@@ -8297,9 +8724,38 @@ function registerDelegateRuntime(api, options) {
8297
8724
  const remainingBudget = () => deadline - Date.now();
8298
8725
  const sessionKey = lifecycleSessionKeyFrom(event, ctx);
8299
8726
  if (sessionKey === void 0) {
8300
- log7.warn("delegate flush skipped: lifecycle event has malformed session key");
8727
+ log8.warn("delegate flush skipped: lifecycle event has malformed session key");
8301
8728
  return false;
8302
8729
  }
8730
+ const pendingObserve = observeChains.get(sessionKey);
8731
+ if (pendingObserve !== void 0) {
8732
+ const drainDeadline = Promise.withResolvers();
8733
+ const timer = setTimeout(drainDeadline.resolve, Math.max(0, Math.min(remainingBudget(), options.flushTimeoutMs / 2)));
8734
+ timer.unref?.();
8735
+ const drained = await Promise.race([
8736
+ pendingObserve.then(() => true),
8737
+ drainDeadline.promise.then(() => false)
8738
+ ]);
8739
+ clearTimeout(timer);
8740
+ if (!drained && !followUpFlushSessions.has(sessionKey)) {
8741
+ const followUp = {};
8742
+ followUpFlushSessions.set(sessionKey, followUp);
8743
+ const releaseIfOwner = () => {
8744
+ if (followUpFlushSessions.get(sessionKey) === followUp) {
8745
+ followUpFlushSessions.delete(sessionKey);
8746
+ }
8747
+ };
8748
+ void pendingObserve.then(async () => {
8749
+ for (let round = 0; round < 8; round += 1) {
8750
+ const queued = observeChains.get(sessionKey);
8751
+ if (queued === void 0) break;
8752
+ await queued;
8753
+ }
8754
+ releaseIfOwner();
8755
+ return flushHandler({ sessionKey }, {});
8756
+ }).catch((err) => log8.warn(`delegate follow-up flush failed: ${String(err)}`)).finally(releaseIfOwner);
8757
+ }
8758
+ }
8303
8759
  const namespaces = await lifecycleSessionNamespacesFrom(
8304
8760
  sessionKey,
8305
8761
  event,
@@ -8322,7 +8778,7 @@ function registerDelegateRuntime(api, options) {
8322
8778
  remainingTimeoutMs: remainingBudget
8323
8779
  });
8324
8780
  } catch (err) {
8325
- log7.warn(`delegate flush-plan ingestion failed: ${String(err)}`);
8781
+ log8.warn(`delegate flush-plan ingestion failed: ${String(err)}`);
8326
8782
  }
8327
8783
  const flushNamespace = async (sessionNamespace) => postJson(
8328
8784
  target,
@@ -8347,7 +8803,7 @@ function registerDelegateRuntime(api, options) {
8347
8803
  (outcome) => outcome.status === "fulfilled" && outcome.value !== null && outcome.value.flushed === true
8348
8804
  );
8349
8805
  };
8350
- if (namespaces.length <= 1 || await supportsBatchFlush(remainingTimeout())) {
8806
+ if (namespaces.length <= 1 || await supportsBatchFlush(Math.max(1, Math.floor(remainingBudget() / 2)))) {
8351
8807
  if (namespaces.length <= 1) {
8352
8808
  const response = await flushNamespace(namespaces[0]);
8353
8809
  return response !== null && response.flushed === true;
@@ -8365,7 +8821,11 @@ function registerDelegateRuntime(api, options) {
8365
8821
  options.serviceId,
8366
8822
  "/engram/v1/lcm/compaction/flush",
8367
8823
  { sessionKey, namespaces: requestNamespaces },
8368
- remainingTimeout()
8824
+ // Half of what is left, like the support probe: the batch is an
8825
+ // optimization whose failure path is `flushIndividually`, and one
8826
+ // that stalled having spent everything would leave those singular
8827
+ // flushes the 1ms floor and buffer the turns anyway.
8828
+ Math.max(1, Math.floor(remainingBudget() / 2))
8369
8829
  );
8370
8830
  if (response === null) {
8371
8831
  invalidateCachedBatchFlushSupport();
@@ -8392,7 +8852,7 @@ function registerDelegateRuntime(api, options) {
8392
8852
  }
8393
8853
  return flushIndividually();
8394
8854
  } catch (err) {
8395
- log7.warn(`delegate flush failed: ${String(err)}`);
8855
+ log8.warn(`delegate flush failed: ${String(err)}`);
8396
8856
  return false;
8397
8857
  }
8398
8858
  };
@@ -8402,135 +8862,13 @@ function registerDelegateRuntime(api, options) {
8402
8862
  api.on("before_reset", flushEndedSession);
8403
8863
  api.on("session_end", flushEndedSession);
8404
8864
  }
8405
- log7.info(
8865
+ log8.info(
8406
8866
  `[${options.serviceId}] bridge mode delegate: memory loop backed by daemon at ${target.host}:${target.port} (embedded orchestrator skipped; tools/CLI stay daemon-side)`
8407
8867
  );
8408
8868
  }
8409
8869
  function activeDelegateAuthorizationOperations(options) {
8410
8870
  return options.allowPromptInjection && options.recallBudgetChars !== 0 ? DEFAULT_DELEGATE_AUTHORIZATION_OPERATIONS : DEFAULT_DELEGATE_AUTHORIZATION_OPERATIONS.filter((operation) => operation !== "recall");
8411
8871
  }
8412
- var delegateNamespaceMigrationChains = /* @__PURE__ */ new Map();
8413
- var queueDelegateNamespaceMigration = (bindingPath, sessionKey, operation) => {
8414
- let sessionChains = delegateNamespaceMigrationChains.get(bindingPath);
8415
- if (sessionChains === void 0) {
8416
- sessionChains = /* @__PURE__ */ new Map();
8417
- delegateNamespaceMigrationChains.set(bindingPath, sessionChains);
8418
- }
8419
- const prior = sessionChains.get(sessionKey) ?? Promise.resolve();
8420
- const run = prior.catch(() => void 0).then(operation);
8421
- const settled = run.then(
8422
- () => void 0,
8423
- () => void 0
8424
- );
8425
- sessionChains.set(sessionKey, settled);
8426
- void settled.then(() => {
8427
- if (sessionChains?.get(sessionKey) !== settled) return;
8428
- sessionChains.delete(sessionKey);
8429
- if (sessionChains.size === 0 && delegateNamespaceMigrationChains.get(bindingPath) === sessionChains) {
8430
- delegateNamespaceMigrationChains.delete(bindingPath);
8431
- }
8432
- });
8433
- return run;
8434
- };
8435
- function createDelegateNamespaceBindingStore(memoryDir, serviceId, isLegacyAdapterActive) {
8436
- const bindingPath = (pluginId) => path9.join(memoryDir, "state", "plugins", pluginId, "session-namespace-bindings.json");
8437
- const primaryPath = bindingPath(serviceId);
8438
- const primary = createFileSessionNamespaceBindingStore(primaryPath);
8439
- if (serviceId !== REMNIC_OPENCLAW_PLUGIN_ID) return primary;
8440
- const legacy = createFileSessionNamespaceBindingStore(
8441
- bindingPath(REMNIC_OPENCLAW_LEGACY_PLUGIN_ID)
8442
- );
8443
- const migratedLegacySessions = /* @__PURE__ */ new Set();
8444
- const rememberMigratedLegacySession = (sessionKey) => {
8445
- if (migratedLegacySessions.has(sessionKey)) return;
8446
- migratedLegacySessions.add(sessionKey);
8447
- while (migratedLegacySessions.size > SESSION_NAMESPACE_BINDING_MAX_ENTRIES) {
8448
- const oldest = migratedLegacySessions.values().next().value;
8449
- if (oldest === void 0) return;
8450
- migratedLegacySessions.delete(oldest);
8451
- }
8452
- };
8453
- const queueSessionMigration = (sessionKey, operation) => queueDelegateNamespaceMigration(primaryPath, sessionKey, operation);
8454
- const readLegacyNamespaces = async (sessionKey, current) => {
8455
- if (!isLegacyAdapterActive() && migratedLegacySessions.has(sessionKey)) return [];
8456
- try {
8457
- const previous = await legacy.namespacesFor(sessionKey);
8458
- if (previous.length === 0) rememberMigratedLegacySession(sessionKey);
8459
- return previous;
8460
- } catch (err) {
8461
- if (current.length > 0) {
8462
- log7.warn(
8463
- `[${serviceId}] delegate legacy namespace read failed; using canonical bindings: ${String(err)}`
8464
- );
8465
- return [];
8466
- }
8467
- throw err;
8468
- }
8469
- };
8470
- const mergeNamespaceHistory = (current, previous) => {
8471
- const merged = [];
8472
- for (const remembered of [...previous, ...current]) {
8473
- const existing = merged.indexOf(remembered);
8474
- if (existing >= 0) merged.splice(existing, 1);
8475
- merged.push(remembered);
8476
- }
8477
- return merged.slice(-SESSION_NAMESPACE_BINDING_MAX_NAMESPACES2);
8478
- };
8479
- const persistNamespaceHistory = async (store, sessionKey, namespaces) => {
8480
- if (store.replace !== void 0) {
8481
- await store.replace(sessionKey, namespaces);
8482
- return;
8483
- }
8484
- for (const namespace of namespaces) {
8485
- await store.remember(sessionKey, namespace);
8486
- }
8487
- };
8488
- const completeLegacyMigration = async (sessionKey) => {
8489
- if (!isLegacyAdapterActive()) {
8490
- try {
8491
- await legacy.replace?.(sessionKey, []);
8492
- } catch (err) {
8493
- log7.warn(`[${serviceId}] delegate legacy namespace cleanup failed: ${String(err)}`);
8494
- }
8495
- }
8496
- rememberMigratedLegacySession(sessionKey);
8497
- };
8498
- return {
8499
- async namespacesFor(sessionKey) {
8500
- return queueSessionMigration(sessionKey, async () => {
8501
- const current = await primary.namespacesFor(sessionKey);
8502
- const previous = await readLegacyNamespaces(sessionKey, current);
8503
- if (previous.length === 0) return current;
8504
- const merged = mergeNamespaceHistory(current, previous);
8505
- const hasMissingLegacy = previous.some((remembered) => !current.includes(remembered));
8506
- if (!hasMissingLegacy) {
8507
- await completeLegacyMigration(sessionKey);
8508
- return current;
8509
- }
8510
- try {
8511
- await persistNamespaceHistory(primary, sessionKey, merged);
8512
- await completeLegacyMigration(sessionKey);
8513
- } catch (err) {
8514
- log7.warn(`[${serviceId}] delegate namespace migration failed: ${String(err)}`);
8515
- }
8516
- return merged;
8517
- });
8518
- },
8519
- async remember(sessionKey, namespace) {
8520
- return queueSessionMigration(sessionKey, async () => {
8521
- const current = await primary.namespacesFor(sessionKey);
8522
- const previous = await readLegacyNamespaces(sessionKey, current);
8523
- if (previous.length === 0) {
8524
- await primary.remember(sessionKey, namespace);
8525
- return;
8526
- }
8527
- const merged = mergeNamespaceHistory([...current, namespace], previous);
8528
- await persistNamespaceHistory(primary, sessionKey, merged);
8529
- await completeLegacyMigration(sessionKey);
8530
- });
8531
- }
8532
- };
8533
- }
8534
8872
  var delegateHookApiServices = /* @__PURE__ */ new WeakMap();
8535
8873
  var delegateActiveServiceIds = /* @__PURE__ */ new Set();
8536
8874
  var delegateEmbeddedFallbackApis = /* @__PURE__ */ new WeakSet();
@@ -8539,13 +8877,13 @@ var delegateAuthorizationPreflightServices = /* @__PURE__ */ new WeakMap();
8539
8877
  function maybeRegisterDelegateRuntime(api, options, deps = { checkHealth: checkDaemonHealthSync }) {
8540
8878
  const boundServices = delegateHookApiServices.get(api);
8541
8879
  if (boundServices?.has(options.serviceId)) {
8542
- log7.debug(
8880
+ log8.debug(
8543
8881
  `delegate register: ${options.serviceId} already has hooks bound on this api \u2014 skipping duplicate registration`
8544
8882
  );
8545
8883
  return true;
8546
8884
  }
8547
8885
  if (delegateEmbeddedFallbackApis.has(api)) {
8548
- log7.debug(
8886
+ log8.debug(
8549
8887
  `delegate register: ${options.serviceId} previously fell back to embedded on this api \u2014 staying embedded to avoid stacking memory paths`
8550
8888
  );
8551
8889
  return false;
@@ -8559,11 +8897,11 @@ function maybeRegisterDelegateRuntime(api, options, deps = { checkHealth: checkD
8559
8897
  bridge = resolveBridgeMode(options.configBridgeMode, {
8560
8898
  memoryDir: options.memoryDir,
8561
8899
  timeoutMs: bridgeHealthTimeoutMs,
8562
- onSkip: (reason) => log7.info(`[${options.serviceId}] bridge mode auto: staying embedded \u2014 ${reason}`)
8900
+ onSkip: (reason) => log8.info(`[${options.serviceId}] bridge mode auto: staying embedded \u2014 ${reason}`)
8563
8901
  });
8564
8902
  } catch (err) {
8565
8903
  const wantedDelegate = requestedDelegate(options.configBridgeMode);
8566
- log7.error(
8904
+ log8.error(
8567
8905
  wantedDelegate ? `${String(err)} \u2014 falling back to the embedded runtime` : `${String(err)} \u2014 the deployment is embedded, so this only affects delegate mode`
8568
8906
  );
8569
8907
  if (!options.passive) delegateEmbeddedFallbackApis.add(api);
@@ -8571,7 +8909,7 @@ function maybeRegisterDelegateRuntime(api, options, deps = { checkHealth: checkD
8571
8909
  }
8572
8910
  if (bridge.mode !== "delegate") {
8573
8911
  if (delegateBoundApis.has(api)) {
8574
- log7.warn(
8912
+ log8.warn(
8575
8913
  `[${options.serviceId}] bridge mode resolved embedded, but a sibling service already bound delegate hooks on this api \u2014 reusing them instead of stacking an embedded runtime`
8576
8914
  );
8577
8915
  return true;
@@ -8579,25 +8917,39 @@ function maybeRegisterDelegateRuntime(api, options, deps = { checkHealth: checkD
8579
8917
  if (!options.passive) {
8580
8918
  delegateEmbeddedFallbackApis.add(api);
8581
8919
  if (resolveRequestedBridgeMode(options.configBridgeMode) === "auto") {
8582
- log7.info(
8920
+ log8.info(
8583
8921
  `[${options.serviceId}] bridge mode auto: embedded hooks are bound on this api \u2014 a daemon that starts later is picked up on the next gateway restart`
8584
8922
  );
8585
8923
  }
8586
8924
  }
8587
8925
  return false;
8588
8926
  }
8589
- if (!bridge.healthVerified && !deps.checkHealth(bridge.daemonHost, bridge.daemonPort, bridgeHealthTimeoutMs)) {
8590
- if (delegateBoundApis.has(api)) {
8591
- log7.warn(
8592
- `[${options.serviceId}] no healthy daemon at ${bridge.daemonHost}:${bridge.daemonPort}, but a sibling service already bound delegate hooks on this api \u2014 reusing them instead of stacking an embedded runtime`
8927
+ if (!bridge.healthVerified) {
8928
+ const hosts = bridge.daemonHostFallback === void 0 ? [bridge.daemonHost] : [bridge.daemonHost, bridge.daemonHostFallback];
8929
+ const preflightDeadline = Date.now() + bridgeHealthTimeoutMs;
8930
+ const healthyHost = hosts.find((host, index) => {
8931
+ const remaining = index === 0 ? bridgeHealthTimeoutMs : preflightDeadline - Date.now();
8932
+ return remaining > 0 && deps.checkHealth(host, bridge.daemonPort, remaining);
8933
+ });
8934
+ if (healthyHost !== void 0 && healthyHost !== bridge.daemonHost) {
8935
+ log8.info(
8936
+ `[${options.serviceId}] bridge mode delegate: loopback refused, dialing the configured address ${healthyHost}`
8593
8937
  );
8594
- return true;
8938
+ bridge = { ...bridge, daemonHost: healthyHost };
8939
+ }
8940
+ if (healthyHost === void 0) {
8941
+ if (delegateBoundApis.has(api)) {
8942
+ log8.warn(
8943
+ `[${options.serviceId}] no healthy daemon at ${hosts.join("/")}:${bridge.daemonPort}, but a sibling service already bound delegate hooks on this api \u2014 reusing them instead of stacking an embedded runtime`
8944
+ );
8945
+ return true;
8946
+ }
8947
+ delegateEmbeddedFallbackApis.add(api);
8948
+ log8.error(
8949
+ `bridge mode delegate requested but no healthy daemon at ${hosts.join("/")}:${bridge.daemonPort} \u2014 falling back to the embedded runtime`
8950
+ );
8951
+ return false;
8595
8952
  }
8596
- delegateEmbeddedFallbackApis.add(api);
8597
- log7.error(
8598
- `bridge mode delegate requested but no healthy daemon at ${bridge.daemonHost}:${bridge.daemonPort} \u2014 falling back to the embedded runtime`
8599
- );
8600
- return false;
8601
8953
  }
8602
8954
  if (!options.passive) {
8603
8955
  (boundServices ?? delegateHookApiServices.set(api, /* @__PURE__ */ new Set()).get(api))?.add(
@@ -8607,9 +8959,9 @@ function maybeRegisterDelegateRuntime(api, options, deps = { checkHealth: checkD
8607
8959
  delegateBoundApis.add(api);
8608
8960
  }
8609
8961
  const toggleStore = options.sessionTogglesEnabled ? createFileToggleStore(
8610
- path9.join(options.memoryDir, "state", "plugins", options.serviceId, "session-toggles.json"),
8962
+ path11.join(options.memoryDir, "state", "plugins", options.serviceId, "session-toggles.json"),
8611
8963
  {
8612
- secondaryReadOnlyPath: options.respectBundledActiveMemoryToggle ? path9.join(options.memoryDir, "state", "plugins", "active-memory", "session-toggles.json") : void 0
8964
+ secondaryReadOnlyPath: options.respectBundledActiveMemoryToggle ? path11.join(options.memoryDir, "state", "plugins", "active-memory", "session-toggles.json") : void 0
8613
8965
  }
8614
8966
  ) : null;
8615
8967
  const target = daemonTargetFor(bridge);
@@ -8633,6 +8985,8 @@ function maybeRegisterDelegateRuntime(api, options, deps = { checkHealth: checkD
8633
8985
  cwd: options.cwd,
8634
8986
  projectTag: options.projectTag,
8635
8987
  flushOnResetEnabled: options.flushOnResetEnabled,
8988
+ openclawToolsEnabled: options.openclawToolsEnabled,
8989
+ openclawToolSnippetMaxChars: options.openclawToolSnippetMaxChars,
8636
8990
  capability: options.capability,
8637
8991
  recallTimeoutMs: 25e3,
8638
8992
  observeTimeoutMs: 12e4,
@@ -8652,16 +9006,16 @@ function maybeRegisterDelegateRuntime(api, options, deps = { checkHealth: checkD
8652
9006
  void probe(target, "", operations).then((result) => {
8653
9007
  if (result.state === "authorized") return;
8654
9008
  if (result.state === "unauthorized") {
8655
- log7.warn(
9009
+ log8.warn(
8656
9010
  `delegate authorization preflight rejected ${operationLabel} (${result.status}; token source: ${result.tokenSource}) \u2014 runtime remains active`
8657
9011
  );
8658
9012
  return;
8659
9013
  }
8660
- log7.warn(
9014
+ log8.warn(
8661
9015
  `delegate authorization preflight could not verify ${operationLabel} (token source: ${result.tokenSource}) \u2014 runtime remains active`
8662
9016
  );
8663
9017
  }).catch(() => {
8664
- log7.warn("delegate authorization preflight could not complete \u2014 runtime remains active");
9018
+ log8.warn("delegate authorization preflight could not complete \u2014 runtime remains active");
8665
9019
  });
8666
9020
  }
8667
9021
  return true;
@@ -8782,23 +9136,23 @@ __export(resolve_provider_secret_exports, {
8782
9136
  findGatewayRuntimeModules: () => findGatewayRuntimeModules
8783
9137
  });
8784
9138
  __reExport(resolve_provider_secret_exports, resolve_provider_secret_star);
8785
- import path10 from "path";
9139
+ import path12 from "path";
8786
9140
  import * as resolve_provider_secret_star from "@remnic/core/resolve-provider-secret";
8787
9141
  async function findGatewayRuntimeModules(filePrefix) {
8788
9142
  const { existsSync, readFileSync, readdirSync, realpathSync: realpathSync2 } = await import("fs");
8789
9143
  const { createRequire: createRequire2 } = await import("module");
8790
9144
  const candidates = [];
8791
9145
  const isWithinRoot = (root, candidate) => {
8792
- const relative = path10.relative(root, candidate);
8793
- return relative.length === 0 || !relative.startsWith("..") && !path10.isAbsolute(relative);
9146
+ const relative = path12.relative(root, candidate);
9147
+ return relative.length === 0 || !relative.startsWith("..") && !path12.isAbsolute(relative);
8794
9148
  };
8795
9149
  let packageRoot;
8796
9150
  try {
8797
9151
  const req = createRequire2(import.meta.url);
8798
9152
  const openclawEntrypoint = realpathSync2(req.resolve("openclaw"));
8799
- let currentDir = path10.dirname(openclawEntrypoint);
9153
+ let currentDir = path12.dirname(openclawEntrypoint);
8800
9154
  while (true) {
8801
- const packageJsonPath = path10.join(currentDir, "package.json");
9155
+ const packageJsonPath = path12.join(currentDir, "package.json");
8802
9156
  if (existsSync(packageJsonPath)) {
8803
9157
  const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
8804
9158
  if (packageJson.name !== "openclaw") {
@@ -8807,7 +9161,7 @@ async function findGatewayRuntimeModules(filePrefix) {
8807
9161
  packageRoot = realpathSync2(currentDir);
8808
9162
  break;
8809
9163
  }
8810
- const parent = path10.dirname(currentDir);
9164
+ const parent = path12.dirname(currentDir);
8811
9165
  if (parent === currentDir) {
8812
9166
  return [];
8813
9167
  }
@@ -8817,14 +9171,14 @@ async function findGatewayRuntimeModules(filePrefix) {
8817
9171
  return [];
8818
9172
  }
8819
9173
  try {
8820
- const distDir = realpathSync2(path10.join(packageRoot, "dist"));
9174
+ const distDir = realpathSync2(path12.join(packageRoot, "dist"));
8821
9175
  if (!isWithinRoot(packageRoot, distDir)) {
8822
9176
  return [];
8823
9177
  }
8824
9178
  const files = readdirSync(distDir);
8825
9179
  for (const f of files) {
8826
9180
  if (f.startsWith(filePrefix) && f.endsWith(".js")) {
8827
- const candidate = realpathSync2(path10.join(distDir, f));
9181
+ const candidate = realpathSync2(path12.join(distDir, f));
8828
9182
  if (isWithinRoot(packageRoot, candidate) && isWithinRoot(distDir, candidate)) {
8829
9183
  candidates.push(candidate);
8830
9184
  }
@@ -9080,7 +9434,7 @@ async function loadOpenClawProviderAuthRuntimeModule() {
9080
9434
  if (typeof mod.resolveApiKeyForProvider === "function" || typeof mod.getRuntimeAuthForModel === "function") {
9081
9435
  providerAuthRuntimeModule = mod;
9082
9436
  providerAuthRuntimeLoaded = true;
9083
- log8.debug("loaded OpenClaw provider auth runtime module");
9437
+ log9.debug("loaded OpenClaw provider auth runtime module");
9084
9438
  return providerAuthRuntimeModule;
9085
9439
  }
9086
9440
  } catch {
@@ -9089,7 +9443,7 @@ async function loadOpenClawProviderAuthRuntimeModule() {
9089
9443
  } catch {
9090
9444
  }
9091
9445
  providerAuthRuntimeNextRetryAt = Date.now() + SECRET_REF_RESOLVER_RETRY_BACKOFF_MS;
9092
- log8.debug(
9446
+ log9.debug(
9093
9447
  `OpenClaw provider auth runtime not available \u2014 will retry after ${SECRET_REF_RESOLVER_RETRY_BACKOFF_MS / 1e3}s`
9094
9448
  );
9095
9449
  return null;
@@ -9128,7 +9482,7 @@ async function loadOpenClawSecretRefResolver() {
9128
9482
  if (typeof fn === "function") {
9129
9483
  secretRefResolver = fn;
9130
9484
  secretRefResolverLoaded = true;
9131
- log8.debug(
9485
+ log9.debug(
9132
9486
  `loaded OpenClaw SecretRef resolver "${exportName}" from ${prefix}*.js`
9133
9487
  );
9134
9488
  return secretRefResolver;
@@ -9141,7 +9495,7 @@ async function loadOpenClawSecretRefResolver() {
9141
9495
  } catch {
9142
9496
  }
9143
9497
  secretRefResolverNextRetryAt = Date.now() + SECRET_REF_RESOLVER_RETRY_BACKOFF_MS;
9144
- log8.debug(
9498
+ log9.debug(
9145
9499
  `OpenClaw SecretRef resolver not available \u2014 will retry after ${SECRET_REF_RESOLVER_RETRY_BACKOFF_MS / 1e3}s`
9146
9500
  );
9147
9501
  return null;
@@ -9167,7 +9521,7 @@ function resolveOpenClawConfigFilePath() {
9167
9521
  if (explicitConfigPath && explicitConfigPath.length > 0) {
9168
9522
  return expandTildePath3(explicitConfigPath);
9169
9523
  }
9170
- return path11.join(resolveHomeDir3(), ".openclaw", "openclaw.json");
9524
+ return path13.join(resolveHomeDir3(), ".openclaw", "openclaw.json");
9171
9525
  }
9172
9526
  function coerceRawConfigBoolean(value) {
9173
9527
  if (typeof value === "boolean") return value;
@@ -9184,7 +9538,7 @@ function loadPluginEntryFromFile(pluginId) {
9184
9538
  const config = JSON.parse(content);
9185
9539
  return resolveRemnicPluginEntry(config, pluginId);
9186
9540
  } catch (err) {
9187
- log8.warn(`Failed to load config from file: ${err}`);
9541
+ log9.warn(`Failed to load config from file: ${err}`);
9188
9542
  return void 0;
9189
9543
  }
9190
9544
  }
@@ -9202,7 +9556,7 @@ function loadRawConfigFromFile() {
9202
9556
  const config = JSON.parse(content);
9203
9557
  return config && typeof config === "object" ? config : void 0;
9204
9558
  } catch (err) {
9205
- log8.warn(`Failed to load raw OpenClaw config from file: ${err}`);
9559
+ log9.warn(`Failed to load raw OpenClaw config from file: ${err}`);
9206
9560
  return void 0;
9207
9561
  }
9208
9562
  }
@@ -9214,10 +9568,10 @@ function readPluginHooksPolicy(apiConfig, pluginId) {
9214
9568
  }
9215
9569
  async function maybeRegisterLiveConnectorCron(orchestrator) {
9216
9570
  if (!hasEnabledLiveConnectorConfig(orchestrator.config.connectors)) return;
9217
- const jobsPath = path11.join(resolveHomeDir3(), ".openclaw", "cron", "jobs.json");
9571
+ const jobsPath = path13.join(resolveHomeDir3(), ".openclaw", "cron", "jobs.json");
9218
9572
  try {
9219
9573
  if (!fileExistsNow(jobsPath)) {
9220
- log8.debug("live connectors cron: jobs.json not found, skipping auto-register");
9574
+ log9.debug("live connectors cron: jobs.json not found, skipping auto-register");
9221
9575
  return;
9222
9576
  }
9223
9577
  const result = await ensureLiveConnectorCron(jobsPath, {
@@ -9225,12 +9579,12 @@ async function maybeRegisterLiveConnectorCron(orchestrator) {
9225
9579
  connectors: orchestrator.config.connectors
9226
9580
  });
9227
9581
  if (result.created || result.updated) {
9228
- log8.info(`live connectors cron ${result.created ? "auto-registered" : "reconciled"} (${result.jobId})`);
9582
+ log9.info(`live connectors cron ${result.created ? "auto-registered" : "reconciled"} (${result.jobId})`);
9229
9583
  } else {
9230
- log8.debug("live connectors cron already exists, skipping auto-register");
9584
+ log9.debug("live connectors cron already exists, skipping auto-register");
9231
9585
  }
9232
9586
  } catch (err) {
9233
- log8.debug(`live connectors cron auto-register error: ${err}`);
9587
+ log9.debug(`live connectors cron auto-register error: ${err}`);
9234
9588
  }
9235
9589
  }
9236
9590
  function isBundledActiveMemoryEnabledForAgent(runtimeConfig, fileBackedRuntimeConfig, agentId) {
@@ -9399,13 +9753,13 @@ function registerOpenClawHostEmbeddingProvider(params) {
9399
9753
  }).then((result) => {
9400
9754
  providerInstance = result && typeof result === "object" ? result.provider ?? null : null;
9401
9755
  if (!providerInstance) {
9402
- log8.debug(
9756
+ log9.debug(
9403
9757
  `OpenClaw host embedding provider ${selected.adapter.id} did not create a provider; using Remnic fallback embeddings`
9404
9758
  );
9405
9759
  }
9406
9760
  return providerInstance;
9407
9761
  }).catch((error) => {
9408
- log8.debug(
9762
+ log9.debug(
9409
9763
  `OpenClaw host embedding provider ${selected.adapter.id} unavailable: ${error}`
9410
9764
  );
9411
9765
  return null;
@@ -9437,7 +9791,7 @@ function registerOpenClawHostEmbeddingProvider(params) {
9437
9791
  }
9438
9792
  };
9439
9793
  const unregister = registerHostEmbeddingProvider(cfg.memoryDir, hostProvider);
9440
- log8.info(
9794
+ log9.info(
9441
9795
  `registered OpenClaw host embedding provider bridge (${selected.adapter.id}) for ${serviceId}`
9442
9796
  );
9443
9797
  return unregister;
@@ -9572,12 +9926,14 @@ async function embedBatchWithOpenClawProvider(kind, provider, texts, options) {
9572
9926
  }
9573
9927
  var sdkCaps;
9574
9928
  var NON_RUNTIME_REGISTRATION_MODES = /* @__PURE__ */ new Set([
9575
- "discovery",
9576
- "tool-discovery",
9577
9929
  "setup-only",
9578
9930
  "setup-runtime",
9579
9931
  "cli-metadata"
9580
9932
  ]);
9933
+ var DISCOVERY_REGISTRATION_MODES = /* @__PURE__ */ new Set([
9934
+ "discovery",
9935
+ "tool-discovery"
9936
+ ]);
9581
9937
  function isNonRuntimeRegistrationMode(mode) {
9582
9938
  return typeof mode === "string" && NON_RUNTIME_REGISTRATION_MODES.has(mode);
9583
9939
  }
@@ -9592,22 +9948,23 @@ var pluginDefinition = {
9592
9948
  const keys = buildServiceKeys(serviceId);
9593
9949
  initLogger(api.logger, false);
9594
9950
  sdkCaps = detectSdkCapabilities(api);
9595
- log8.info(
9951
+ log9.info(
9596
9952
  `SDK detection: version=${sdkCaps.sdkVersion}, beforePromptBuild=${sdkCaps.hasBeforePromptBuild}, memoryPromptSection=${sdkCaps.hasRegisterMemoryPromptSection}, memoryCapability=${sdkCaps.hasRegisterMemoryCapability}, typedHooks=${sdkCaps.hasTypedHooks}`
9597
9953
  );
9598
9954
  if (isNonRuntimeRegistrationMode(sdkCaps.registrationMode)) {
9599
- log8.info(
9955
+ log9.info(
9600
9956
  `registrationMode=${sdkCaps.registrationMode} \u2014 skipping runtime initialization`
9601
9957
  );
9602
9958
  return;
9603
9959
  }
9604
- const disableRegisterMigration = readEnvVar("REMNIC_DISABLE_REGISTER_MIGRATION") === "1" || readEnvVar("OPENCLAW_ENGRAM_DISABLE_REGISTER_MIGRATION") === "1";
9960
+ const discoveryPass = typeof sdkCaps.registrationMode === "string" && DISCOVERY_REGISTRATION_MODES.has(sdkCaps.registrationMode);
9961
+ const disableRegisterMigration = readEnvVar("REMNIC_DISABLE_REGISTER_MIGRATION") === "1" || readEnvVar("OPENCLAW_ENGRAM_DISABLE_REGISTER_MIGRATION") === "1" || discoveryPass;
9605
9962
  if (!disableRegisterMigration) {
9606
9963
  const migrationPromise = globalThis[ENGRAM_MIGRATION_PROMISE] ??= migrateFromEngram({
9607
9964
  quiet: true,
9608
- logger: (message) => log8.info(message)
9965
+ logger: (message) => log9.info(message)
9609
9966
  }).catch((error) => {
9610
- log8.warn(`register migration failed: ${error}`);
9967
+ log9.warn(`register migration failed: ${error}`);
9611
9968
  });
9612
9969
  void migrationPromise;
9613
9970
  }
@@ -9641,10 +9998,10 @@ var pluginDefinition = {
9641
9998
  cfg.providerApiKeyResolver = resolveOpenClawProviderApiKey;
9642
9999
  cfg.runtimeAuthForModelResolver = getOpenClawRuntimeAuthForModel;
9643
10000
  initLogger(api.logger, cfg.debug);
9644
- log8.info(
10001
+ log9.info(
9645
10002
  `initialized (debug=${cfg.debug}, qmdEnabled=${cfg.qmdEnabled}, transcriptEnabled=${cfg.transcriptEnabled}, hourlySummariesEnabled=${cfg.hourlySummariesEnabled})`
9646
10003
  );
9647
- log8.debug(
10004
+ log9.debug(
9648
10005
  `init llm routing (modelSource=${cfg.modelSource}, localLlmEnabled=${cfg.localLlmEnabled}${cfg.localLlmFastEnabled ? `, fastLlm=${cfg.localLlmFastModel || "(primary)"}` : ""})`
9649
10006
  );
9650
10007
  const fileBackedRawRuntimeConfig = loadRawConfigFromFile();
@@ -9658,7 +10015,7 @@ var pluginDefinition = {
9658
10015
  });
9659
10016
  const passiveMode = slotValidationMode === "passive";
9660
10017
  if (passiveMode) {
9661
- log8.info(
10018
+ log9.info(
9662
10019
  `[remnic] memory slot not assigned to ${serviceId}; running passively`
9663
10020
  );
9664
10021
  }
@@ -9689,6 +10046,8 @@ var pluginDefinition = {
9689
10046
  shouldSkipRecall: (sk) => shouldSkipRecallForSession(sk, cfg),
9690
10047
  cwd: getOpenClawRuntimeWorkspaceDir(api),
9691
10048
  flushOnResetEnabled: cfg.flushOnResetEnabled,
10049
+ openclawToolsEnabled: cfg.openclawToolsEnabled,
10050
+ openclawToolSnippetMaxChars: cfg.openclawToolSnippetMaxChars,
9692
10051
  supportPassportModelRoute: delegateSupportPassportGatewayRoute ?? void 0,
9693
10052
  // Memory-slot capability inputs. Mirrors the embedded derivation: the
9694
10053
  // registration-time runtime agent owns this memory, and QMD is the
@@ -9740,14 +10099,14 @@ var pluginDefinition = {
9740
10099
  }
9741
10100
  const hookApis = globalThis[keys.HOOK_APIS] ??= /* @__PURE__ */ new WeakSet();
9742
10101
  if (hookApis.has(api)) {
9743
- log8.debug(
10102
+ log9.debug(
9744
10103
  "register: this api already has hooks bound \u2014 skipping duplicate hook registration"
9745
10104
  );
9746
10105
  return;
9747
10106
  }
9748
10107
  hookApis.add(api);
9749
10108
  if (!isFirstRegistration) {
9750
- log8.debug(
10109
+ log9.debug(
9751
10110
  "register called again (new registry); re-registering hooks with shared orchestrator"
9752
10111
  );
9753
10112
  }
@@ -9783,9 +10142,9 @@ var pluginDefinition = {
9783
10142
  emitLegacyTools: cfg.emitLegacyTools
9784
10143
  });
9785
10144
  globalThis[keys.ACCESS_HTTP_SERVER] = accessHttpServer;
9786
- const pluginStateDir = path11.join(cfg.memoryDir, "state", "plugins", serviceId);
9787
- const togglePrimaryPath = path11.join(pluginStateDir, "session-toggles.json");
9788
- const toggleSecondaryPath = cfg.respectBundledActiveMemoryToggle ? path11.join(cfg.memoryDir, "state", "plugins", "active-memory", "session-toggles.json") : void 0;
10145
+ const pluginStateDir = path13.join(cfg.memoryDir, "state", "plugins", serviceId);
10146
+ const togglePrimaryPath = path13.join(pluginStateDir, "session-toggles.json");
10147
+ const toggleSecondaryPath = cfg.respectBundledActiveMemoryToggle ? path13.join(cfg.memoryDir, "state", "plugins", "active-memory", "session-toggles.json") : void 0;
9789
10148
  const sessionToggleStore = createFileToggleStore2(togglePrimaryPath, {
9790
10149
  secondaryReadOnlyPath: toggleSecondaryPath
9791
10150
  });
@@ -9807,11 +10166,11 @@ var pluginDefinition = {
9807
10166
  }
9808
10167
  function resolveDreamJournalPath(runtimeWorkspaceDir) {
9809
10168
  const workspaceRoot = resolveWorkspaceRoot(runtimeWorkspaceDir);
9810
- return path11.isAbsolute(cfg.dreaming.journalPath) ? cfg.dreaming.journalPath : path11.join(workspaceRoot, cfg.dreaming.journalPath);
10169
+ return path13.isAbsolute(cfg.dreaming.journalPath) ? cfg.dreaming.journalPath : path13.join(workspaceRoot, cfg.dreaming.journalPath);
9811
10170
  }
9812
10171
  function resolveHeartbeatJournalPath(runtimeWorkspaceDir) {
9813
10172
  const workspaceRoot = resolveWorkspaceRoot(runtimeWorkspaceDir);
9814
- return path11.isAbsolute(cfg.heartbeat.journalPath) ? cfg.heartbeat.journalPath : path11.join(workspaceRoot, cfg.heartbeat.journalPath);
10173
+ return path13.isAbsolute(cfg.heartbeat.journalPath) ? cfg.heartbeat.journalPath : path13.join(workspaceRoot, cfg.heartbeat.journalPath);
9815
10174
  }
9816
10175
  const existingFlushPlanProcessingChains = globalThis[keys.FLUSH_PLAN_PROCESSING_CHAINS];
9817
10176
  const flushPlanProcessingChains = existingFlushPlanProcessingChains instanceof Map ? existingFlushPlanProcessingChains : /* @__PURE__ */ new Map();
@@ -9831,7 +10190,7 @@ var pluginDefinition = {
9831
10190
  }
9832
10191
  const remainingMs = Math.max(0, deadlineMs - Date.now());
9833
10192
  if (remainingMs === 0) {
9834
- log8.warn(
10193
+ log9.warn(
9835
10194
  `OpenClaw flush-plan processing timed out before queue wait for ${reason}`
9836
10195
  );
9837
10196
  return Promise.resolve();
@@ -9841,7 +10200,7 @@ var pluginDefinition = {
9841
10200
  task,
9842
10201
  new Promise((resolve) => {
9843
10202
  timeout = setTimeout(() => {
9844
- log8.warn(
10203
+ log9.warn(
9845
10204
  `OpenClaw flush-plan processing timed out while waiting for ${reason}; current drain remains fenced until it settles`
9846
10205
  );
9847
10206
  resolve();
@@ -9853,7 +10212,7 @@ var pluginDefinition = {
9853
10212
  }
9854
10213
  async function runOpenClawFlushPlanProcessing(reason, workspaceRoot, deadlineMs) {
9855
10214
  if (typeof deadlineMs === "number" && Date.now() >= deadlineMs) {
9856
- log8.warn(
10215
+ log9.warn(
9857
10216
  `OpenClaw flush-plan processing timed out before starting for ${reason}`
9858
10217
  );
9859
10218
  return;
@@ -9865,26 +10224,26 @@ var pluginDefinition = {
9865
10224
  serviceId,
9866
10225
  ingestor: orchestrator,
9867
10226
  logger: {
9868
- debug: (message) => log8.debug(message),
9869
- info: (message) => log8.info(message),
9870
- warn: (message) => log8.warn(message)
10227
+ debug: (message) => log9.debug(message),
10228
+ info: (message) => log9.info(message),
10229
+ warn: (message) => log9.warn(message)
9871
10230
  },
9872
10231
  reason,
9873
10232
  deadlineMs,
9874
10233
  maxTurnChars: cfg.extractionMaxTurnChars
9875
10234
  });
9876
10235
  if (result.status === "processed" || result.status === "processed_preserved_tail" || result.status === "processed_marker_recovered" || result.status === "processed_marker_recovered_tail" || result.status === "processed_cleanup_deferred") {
9877
- log8.info(
10236
+ log9.info(
9878
10237
  `OpenClaw flush-plan ${result.status}: ${result.bytesProcessed ?? 0} bytes` + (result.preservedBytes ? ` (${result.preservedBytes} bytes preserved)` : "")
9879
10238
  );
9880
10239
  } else if (result.status === "skipped") {
9881
- log8.warn(
10240
+ log9.warn(
9882
10241
  `OpenClaw flush-plan processing skipped: ${result.reason ?? "unknown reason"}`
9883
10242
  );
9884
10243
  }
9885
10244
  } catch (error) {
9886
10245
  const detail = displayErrorDetail(error) || "unknown error";
9887
- log8.warn(`OpenClaw flush-plan processing failed: ${detail}`);
10246
+ log9.warn(`OpenClaw flush-plan processing failed: ${detail}`);
9888
10247
  }
9889
10248
  }
9890
10249
  async function queueOpenClawFlushPlanProcessing(reason, runtimeWorkspaceDir, options = {}) {
@@ -9965,7 +10324,7 @@ var pluginDefinition = {
9965
10324
  });
9966
10325
  },
9967
10326
  logger: {
9968
- debug: (message) => log8.debug(message)
10327
+ debug: (message) => log9.debug(message)
9969
10328
  }
9970
10329
  });
9971
10330
  }
@@ -9977,7 +10336,7 @@ var pluginDefinition = {
9977
10336
  if (!cfg.dreaming.enabled) return;
9978
10337
  const route = resolveDreamNarrativeRoute(cfg, !!dreamNarrativeClient);
9979
10338
  if (route.kind === "skip") {
9980
- log8.debug(
10339
+ log9.debug(
9981
10340
  "dreaming narrative skipped: no LLM available (set openaiApiKey or use modelSource=gateway)"
9982
10341
  );
9983
10342
  return;
@@ -10008,7 +10367,7 @@ Keep the reflection grounded in the evidence below.
10008
10367
  fallbackLlmRuntimeContextFromConfig2(cfg)
10009
10368
  );
10010
10369
  if (!route.hasExplicitModel && !llm.isAvailable(route.options)) {
10011
- log8.debug(
10370
+ log9.debug(
10012
10371
  "dreaming narrative skipped: no gateway model configured (set dreaming.narrativeModel or taskModelChain)"
10013
10372
  );
10014
10373
  return;
@@ -10032,7 +10391,7 @@ Keep the reflection grounded in the evidence below.
10032
10391
  rawNarrative = typeof response.output_text === "string" ? response.output_text : JSON.stringify(response.output_text ?? "");
10033
10392
  }
10034
10393
  } catch (error) {
10035
- log8.warn(`dreaming narrative generation failed: ${String(error)}`);
10394
+ log9.warn(`dreaming narrative generation failed: ${String(error)}`);
10036
10395
  return;
10037
10396
  }
10038
10397
  const parsed = parseDreamNarrativeResponse(
@@ -10048,12 +10407,11 @@ Keep the reflection grounded in the evidence below.
10048
10407
  });
10049
10408
  await queueDreamSurfaceSync();
10050
10409
  }
10051
- void pruneRecallAuditEntries(
10052
- recallAuditDir,
10053
- cfg.recallTranscriptRetentionDays
10054
- ).catch((error) => {
10055
- log8.debug(`recall audit prune failed: ${String(error)}`);
10056
- });
10410
+ if (!discoveryPass) {
10411
+ void pruneRecallAuditEntries(recallAuditDir, cfg.recallTranscriptRetentionDays).catch((error) => {
10412
+ log9.debug(`recall audit prune failed: ${String(error)}`);
10413
+ });
10414
+ }
10057
10415
  const sessionCommandDescriptors = buildSessionCommandDescriptors(serviceId, {
10058
10416
  toggles: sessionToggleStore,
10059
10417
  getLastRecall: (sessionKey) => orchestrator.getLastRecall(sessionKey),
@@ -10088,7 +10446,7 @@ Keep the reflection grounded in the evidence below.
10088
10446
  timeoutMs: cfg.activeRecallTimeoutMs,
10089
10447
  cacheTtlMs: cfg.activeRecallCacheTtlMs,
10090
10448
  persistTranscripts: cfg.activeRecallPersistTranscripts,
10091
- transcriptDir: path11.isAbsolute(cfg.activeRecallTranscriptDir) ? cfg.activeRecallTranscriptDir : path11.join(pluginStateDir, cfg.activeRecallTranscriptDir),
10449
+ transcriptDir: path13.isAbsolute(cfg.activeRecallTranscriptDir) ? cfg.activeRecallTranscriptDir : path13.join(pluginStateDir, cfg.activeRecallTranscriptDir),
10092
10450
  entityGraphDepth: cfg.activeRecallEntityGraphDepth,
10093
10451
  includeCausalTrajectories: cfg.activeRecallIncludeCausalTrajectories,
10094
10452
  includeDaySummary: cfg.activeRecallIncludeDaySummary,
@@ -10499,7 +10857,7 @@ Keep the reflection grounded in the evidence below.
10499
10857
  return;
10500
10858
  }
10501
10859
  if (typeof orchestrator.flushSession !== "function") {
10502
- log8.warn("codexCompat provider-switch flush unavailable; preserving binding");
10860
+ log9.warn("codexCompat provider-switch flush unavailable; preserving binding");
10503
10861
  return;
10504
10862
  }
10505
10863
  try {
@@ -10509,7 +10867,7 @@ Keep the reflection grounded in the evidence below.
10509
10867
  });
10510
10868
  forgetCodexThread2(sessionKey, sessionIdentity.previousCodexThreadId);
10511
10869
  } catch (error) {
10512
- log8.warn(`codexCompat provider-switch flush failed: ${String(error)}`);
10870
+ log9.warn(`codexCompat provider-switch flush failed: ${String(error)}`);
10513
10871
  }
10514
10872
  }
10515
10873
  async function flushAndForgetRememberedCodexThreadOnMetadataLoss(sessionKey, sessionIdentity) {
@@ -10527,7 +10885,7 @@ Keep the reflection grounded in the evidence below.
10527
10885
  return;
10528
10886
  }
10529
10887
  if (typeof orchestrator.flushSession !== "function") {
10530
- log8.warn("codexCompat metadata-loss flush unavailable; preserving binding");
10888
+ log9.warn("codexCompat metadata-loss flush unavailable; preserving binding");
10531
10889
  return;
10532
10890
  }
10533
10891
  try {
@@ -10537,7 +10895,7 @@ Keep the reflection grounded in the evidence below.
10537
10895
  });
10538
10896
  forgetCodexThread2(sessionKey, rememberedThreadId);
10539
10897
  } catch (error) {
10540
- log8.warn(`codexCompat metadata-loss flush failed: ${String(error)}`);
10898
+ log9.warn(`codexCompat metadata-loss flush failed: ${String(error)}`);
10541
10899
  }
10542
10900
  }
10543
10901
  async function resolveBeforeResetBufferKeys(sessionKey, sessionIdentity) {
@@ -10665,7 +11023,7 @@ Keep the reflection grounded in the evidence below.
10665
11023
  rememberCodexThread2(sessionKey, sessionIdentity.providerThreadId);
10666
11024
  if (sessionIdentity.isCodex && !codexCompactionModeLogged) {
10667
11025
  const mode = cfg.codexCompat.compactionFlushMode === "auto" ? "auto compaction flush mode (signal + heuristic)" : `${cfg.codexCompat.compactionFlushMode} compaction flush mode`;
10668
- log8.info(
11026
+ log9.info(
10669
11027
  `codexCompat enabled: using ${mode} for bundled Codex sessions`
10670
11028
  );
10671
11029
  codexCompactionModeLogged = true;
@@ -10689,12 +11047,12 @@ Keep the reflection grounded in the evidence below.
10689
11047
  });
10690
11048
  clearCodexCompatCaches2(sessionKey, sessionIdentity.providerThreadId);
10691
11049
  rememberCodexThread2(sessionKey, sessionIdentity.providerThreadId);
10692
- log8.info(
11050
+ log9.info(
10693
11051
  `codexCompat heuristic flush: thread=${sessionIdentity.providerThreadId} messages ${previousCount} -> ${currentCount}`
10694
11052
  );
10695
11053
  } catch (error) {
10696
11054
  shouldPersistMessageCount = false;
10697
- log8.warn(`codexCompat heuristic flush failed: ${String(error)}`);
11055
+ log9.warn(`codexCompat heuristic flush failed: ${String(error)}`);
10698
11056
  }
10699
11057
  }
10700
11058
  if (typeof currentCount === "number" && shouldPersistMessageCount && codexBaselineKey) {
@@ -10705,10 +11063,10 @@ Keep the reflection grounded in the evidence below.
10705
11063
  const runtimeAgent = ctx.runtime?.agent;
10706
11064
  const agentId = ctx?.agentId ?? runtimeAgent?.id ?? "main";
10707
11065
  const verboseRequested = cfg.verboseRecallVisibility !== false && isVerboseRecallRequested(event, ctx);
10708
- log8.debug(
11066
+ log9.debug(
10709
11067
  `${hookLabel}: sessionKey=${sessionKey}, promptLen=${prompt.length}`
10710
11068
  );
10711
- log8.debug(
11069
+ log9.debug(
10712
11070
  `${hookLabel}: cronRecallMode=${cfg.cronRecallMode}, allowlistCount=${cfg.cronRecallAllowlist.length}`
10713
11071
  );
10714
11072
  if (sessionKey.includes(":cron:") && cfg.cronRecallMode === "allowlist") {
@@ -10716,12 +11074,12 @@ Keep the reflection grounded in the evidence below.
10716
11074
  const re = wildcardToRegExp(pattern);
10717
11075
  return re.test(sessionKey);
10718
11076
  });
10719
- log8.debug(
11077
+ log9.debug(
10720
11078
  `${hookLabel}: cron allowlist match=${matchedPattern ? "yes" : "no"} pattern=${matchedPattern ?? "none"}`
10721
11079
  );
10722
11080
  }
10723
11081
  if (shouldSkipRecallForSession(sessionKey, cfg)) {
10724
- log8.debug(
11082
+ log9.debug(
10725
11083
  `${hookLabel}: skip recall for cron session ${sessionKey} (mode=${cfg.cronRecallMode})`
10726
11084
  );
10727
11085
  return;
@@ -10745,7 +11103,7 @@ Keep the reflection grounded in the evidence below.
10745
11103
  injectedChars: 0,
10746
11104
  toggleState: auditToggleState
10747
11105
  }).catch((error) => {
10748
- log8.debug(`recall audit append failed: ${String(error)}`);
11106
+ log9.debug(`recall audit append failed: ${String(error)}`);
10749
11107
  });
10750
11108
  }
10751
11109
  if (!verboseRequested) return;
@@ -10799,7 +11157,7 @@ Keep the reflection grounded in the evidence below.
10799
11157
  const shouldWarnAndSuppressBundledActiveMemoryCollision = cfg.activeRecallEnabled && !cfg.activeRecallAllowChainedActiveMemory && bundledActiveMemoryEnabledForAgent;
10800
11158
  if (shouldWarnAndSuppressBundledActiveMemoryCollision && !warnedBundledActiveMemoryCollisionAgents.has(agentId)) {
10801
11159
  warnedBundledActiveMemoryCollisionAgents.add(agentId);
10802
- log8.warn(
11160
+ log9.warn(
10803
11161
  `active recall suppressed because bundled active-memory plugin is enabled for agent "${agentId}" while activeRecallAllowChainedActiveMemory=false`
10804
11162
  );
10805
11163
  }
@@ -10813,7 +11171,7 @@ Keep the reflection grounded in the evidence below.
10813
11171
  ),
10814
11172
  currentMessage: prompt
10815
11173
  }).catch((error) => {
10816
- log8.debug(`active recall fallback failed: ${String(error)}`);
11174
+ log9.debug(`active recall fallback failed: ${String(error)}`);
10817
11175
  return null;
10818
11176
  });
10819
11177
  const activeRecallLines = activeRecallResult?.summary && activeRecallResult.summary.length > 0 ? ["## Active Recall (Remnic)", "", activeRecallResult.summary, ""] : [];
@@ -10828,7 +11186,7 @@ Keep the reflection grounded in the evidence below.
10828
11186
  }
10829
11187
  });
10830
11188
  recallComposition ??= { context };
10831
- log8.debug(
11189
+ log9.debug(
10832
11190
  `${hookLabel}: recall returned ${context?.length ?? 0} chars`
10833
11191
  );
10834
11192
  const lastRecall = orchestrator.getLastRecall(sessionKey);
@@ -10875,7 +11233,7 @@ Keep the reflection grounded in the evidence below.
10875
11233
  requestedMode: lastRecall?.requestedMode,
10876
11234
  fallbackUsed: lastRecall?.fallbackUsed
10877
11235
  }).catch((error) => {
10878
- log8.debug(`recall audit append failed: ${String(error)}`);
11236
+ log9.debug(`recall audit append failed: ${String(error)}`);
10879
11237
  });
10880
11238
  }
10881
11239
  if (mergedLines.length === 0) return;
@@ -10902,7 +11260,7 @@ Keep the reflection grounded in the evidence below.
10902
11260
  requestedMode: lastRecall?.requestedMode,
10903
11261
  fallbackUsed: lastRecall?.fallbackUsed
10904
11262
  }).catch((error) => {
10905
- log8.debug(`recall audit append failed: ${String(error)}`);
11263
+ log9.debug(`recall audit append failed: ${String(error)}`);
10906
11264
  });
10907
11265
  }
10908
11266
  const verboseLines = verboseRequested ? buildVerboseRecallHeader({
@@ -10922,12 +11280,12 @@ Keep the reflection grounded in the evidence below.
10922
11280
  const memorySectionLines = memoryContext.lines;
10923
11281
  const memoryLines = useMemoryPromptSection ? memorySectionLines : [...auxiliaryLines, ...memorySectionLines];
10924
11282
  const promptWithVerbose = useMemoryPromptSection ? auxiliaryLines.length > 0 ? auxiliaryLines.join("\n").replace(/\n$/, "") : void 0 : auxiliaryLines.length > 0 ? [...auxiliaryLines, ...memorySectionLines].join("\n").replace(/\n$/, "") : memoryContext.prompt;
10925
- log8.debug(
11283
+ log9.debug(
10926
11284
  `${hookLabel}: returning memory context with ${trimmed.length} chars`
10927
11285
  );
10928
11286
  return promptWithVerbose ? { prependSystemContext: promptWithVerbose, memoryLines } : { memoryLines };
10929
11287
  } catch (err) {
10930
- log8.error("recall failed", err);
11288
+ log9.error("recall failed", err);
10931
11289
  lastRecallSummaryBySession.set(sessionKey, null);
10932
11290
  clearCodexCompatCaches2(sessionKey, void 0, {
10933
11291
  preserveMessageCount: true,
@@ -10978,7 +11336,7 @@ Keep the reflection grounded in the evidence below.
10978
11336
  if (useMemoryPromptSection && api.registerMemoryPromptSection) {
10979
11337
  if (cfg.activeRecallEnabled && !globalThis[ACTIVE_RECALL_OVERLAP_WARNED]) {
10980
11338
  globalThis[ACTIVE_RECALL_OVERLAP_WARNED] = true;
10981
- log8.warn(
11339
+ log9.warn(
10982
11340
  "activeRecallEnabled=true while memory-slot prompt injection is active (registerMemoryPromptSection). Prompt injection does NOT require active recall; this runs a second blocking retrieval per turn. Disable activeRecallEnabled unless the secondary pre-reply summary block is intentional (issue #1550)."
10983
11341
  );
10984
11342
  }
@@ -11189,7 +11547,7 @@ Keep the reflection grounded in the evidence below.
11189
11547
  agentIds: capabilityAgentIds
11190
11548
  });
11191
11549
  } catch (err) {
11192
- log8.error("publicArtifacts.listArtifacts failed", err);
11550
+ log9.error("publicArtifacts.listArtifacts failed", err);
11193
11551
  return [];
11194
11552
  }
11195
11553
  }
@@ -11206,14 +11564,14 @@ Keep the reflection grounded in the evidence below.
11206
11564
  }
11207
11565
  const builderDesc = !promptInjectionAllowed ? " (promptBuilder omitted \u2014 injection disabled by policy)" : memoryPromptBuilder ? " and promptBuilder (from registerMemoryPromptSection)" : " and promptBuilder (capability-only fallback)";
11208
11566
  const capabilityDesc = typeof api.registerMemoryCapability === "function" ? `memory capability with publicArtifacts provider${builderDesc}` : "split memory runtime/flush-plan surfaces";
11209
- log8.info(`registered ${capabilityDesc}`);
11567
+ log9.info(`registered ${capabilityDesc}`);
11210
11568
  }
11211
11569
  if (cfg.openclawMessageReceivedCaptureEnabled) {
11212
11570
  api.on(
11213
11571
  "message_received",
11214
11572
  async (event, ctx) => {
11215
11573
  if (cfg.heartbeat.enabled && cfg.heartbeat.gateExtractionDuringHeartbeat && isHeartbeatTrigger2(event, ctx)) {
11216
- log8.debug(
11574
+ log9.debug(
11217
11575
  `message_received: skipping transcript capture during heartbeat run for ${ctx?.sessionKey ?? "default"}`
11218
11576
  );
11219
11577
  return;
@@ -11301,7 +11659,7 @@ Keep the reflection grounded in the evidence below.
11301
11659
  rememberPendingSparseInboundContentFingerprint2(sparseInboundContentFingerprint);
11302
11660
  }
11303
11661
  } catch (err) {
11304
- log8.debug(`message_received transcript append failed: ${err}`);
11662
+ log9.debug(`message_received transcript append failed: ${err}`);
11305
11663
  }
11306
11664
  }
11307
11665
  );
@@ -11312,7 +11670,7 @@ Keep the reflection grounded in the evidence below.
11312
11670
  if (!event.success || !Array.isArray(event.messages)) return;
11313
11671
  if (event.messages.length === 0) return;
11314
11672
  if (cfg.heartbeat.enabled && cfg.heartbeat.gateExtractionDuringHeartbeat && isHeartbeatTrigger2(event, ctx)) {
11315
- log8.debug(
11673
+ log9.debug(
11316
11674
  `agent_end: skipping transcript/extraction buffering during heartbeat run for ${ctx?.sessionKey ?? "default"}`
11317
11675
  );
11318
11676
  return;
@@ -11375,7 +11733,7 @@ Keep the reflection grounded in the evidence below.
11375
11733
  messages
11376
11734
  });
11377
11735
  } catch (error) {
11378
- log8.debug(
11736
+ log9.debug(
11379
11737
  `agent_end objective-state writer skipped due to error: ${error}`
11380
11738
  );
11381
11739
  }
@@ -11493,11 +11851,11 @@ Keep the reflection grounded in the evidence below.
11493
11851
  );
11494
11852
  }
11495
11853
  } catch (lcmErr) {
11496
- log8.debug(`LCM agent_end indexing error: ${lcmErr}`);
11854
+ log9.debug(`LCM agent_end indexing error: ${lcmErr}`);
11497
11855
  }
11498
11856
  }
11499
11857
  } catch (err) {
11500
- log8.error("agent_end processing failed", err);
11858
+ log9.error("agent_end processing failed", err);
11501
11859
  }
11502
11860
  }
11503
11861
  );
@@ -11523,7 +11881,7 @@ Keep the reflection grounded in the evidence below.
11523
11881
  clearCodexCompatCaches2(sessionKey, signalThreadId);
11524
11882
  rememberCodexThread2(sessionKey, signalThreadId);
11525
11883
  } catch (error) {
11526
- log8.warn(`codexCompat signal flush failed: ${String(error)}`);
11884
+ log9.warn(`codexCompat signal flush failed: ${String(error)}`);
11527
11885
  }
11528
11886
  }
11529
11887
  if (orchestrator.lcmEngine?.enabled) {
@@ -11551,7 +11909,7 @@ Keep the reflection grounded in the evidence below.
11551
11909
  );
11552
11910
  await orchestrator.lcmEngine.preCompactionFlush(sessionKey);
11553
11911
  } catch (lcmErr) {
11554
- log8.debug(`LCM before_compaction error: ${lcmErr}`);
11912
+ log9.debug(`LCM before_compaction error: ${lcmErr}`);
11555
11913
  }
11556
11914
  }
11557
11915
  if (!orchestrator.config.checkpointEnabled) {
@@ -11571,10 +11929,10 @@ Keep the reflection grounded in the evidence below.
11571
11929
  turns: checkpointTurns,
11572
11930
  ttl: new Date(Date.now() + 24 * 60 * 60 * 1e3).toISOString()
11573
11931
  });
11574
- log8.info(`saved checkpoint for ${sessionKey} before compaction`);
11932
+ log9.info(`saved checkpoint for ${sessionKey} before compaction`);
11575
11933
  }
11576
11934
  } catch (err) {
11577
- log8.error("before_compaction hook failed", err);
11935
+ log9.error("before_compaction hook failed", err);
11578
11936
  }
11579
11937
  }
11580
11938
  );
@@ -11614,28 +11972,28 @@ Keep the reflection grounded in the evidence below.
11614
11972
  );
11615
11973
  await orchestrator.lcmEngine.verifyPostCompaction(sessionKey);
11616
11974
  } catch (lcmErr) {
11617
- log8.debug(`LCM after_compaction error: ${lcmErr}`);
11975
+ log9.debug(`LCM after_compaction error: ${lcmErr}`);
11618
11976
  }
11619
11977
  }
11620
11978
  void queueOpenClawFlushPlanProcessing("after_compaction", workspaceDir);
11621
11979
  if (!orchestrator.config.compactionResetEnabled) {
11622
- log8.debug(
11980
+ log9.debug(
11623
11981
  `compaction completed for ${sessionKey}, reset disabled \u2014 skipping`
11624
11982
  );
11625
11983
  return;
11626
11984
  }
11627
- log8.info(
11985
+ log9.info(
11628
11986
  `compaction completed for ${sessionKey}, triggering session reset`
11629
11987
  );
11630
11988
  const apiAny = api;
11631
11989
  if (typeof apiAny.resetSession === "function") {
11632
11990
  const result = await apiAny.resetSession(sessionKey, "new");
11633
11991
  if (result?.ok === true) {
11634
- log8.info(
11992
+ log9.info(
11635
11993
  `session reset via API for ${sessionKey}, new sessionId=${result.sessionId}`
11636
11994
  );
11637
11995
  const safeSessionKey = sanitizeSessionKeyForFilename(sessionKey);
11638
- const signalPath = path11.join(
11996
+ const signalPath = path13.join(
11639
11997
  workspaceDir,
11640
11998
  `.compaction-reset-signal-${safeSessionKey}`
11641
11999
  );
@@ -11651,17 +12009,17 @@ Keep the reflection grounded in the evidence below.
11651
12009
  const errorDetail = result && typeof result === "object" && "error" in result ? String(
11652
12010
  result.error ?? "unknown error"
11653
12011
  ) : `invalid result: ${JSON.stringify(result)}`;
11654
- log8.error(
12012
+ log9.error(
11655
12013
  `api.resetSession failed for ${sessionKey}: ${errorDetail}`
11656
12014
  );
11657
12015
  }
11658
12016
  } else {
11659
- log8.error(
12017
+ log9.error(
11660
12018
  `api.resetSession not available \u2014 compaction reset requires OC fork with PR #29985. Session ${sessionKey} will continue without reset.`
11661
12019
  );
11662
12020
  }
11663
12021
  } catch (err) {
11664
- log8.error("after_compaction reset failed", err);
12022
+ log9.error("after_compaction reset failed", err);
11665
12023
  }
11666
12024
  }
11667
12025
  );
@@ -11694,7 +12052,7 @@ Keep the reflection grounded in the evidence below.
11694
12052
  ).catch((error) => {
11695
12053
  if (!flushAbort.signal.aborted) {
11696
12054
  flushFailed = true;
11697
- log8.warn(
12055
+ log9.warn(
11698
12056
  `before_reset flush failed for ${bufferKey}: ${String(error)}`
11699
12057
  );
11700
12058
  }
@@ -11727,7 +12085,7 @@ Keep the reflection grounded in the evidence below.
11727
12085
  );
11728
12086
  }
11729
12087
  if (flushTimedOut) {
11730
- log8.warn(
12088
+ log9.warn(
11731
12089
  `before_reset flush timed out after ${cfg.beforeResetTimeoutMs}ms`
11732
12090
  );
11733
12091
  }
@@ -11742,18 +12100,18 @@ Keep the reflection grounded in the evidence below.
11742
12100
  }
11743
12101
  );
11744
12102
  } catch (error) {
11745
- log8.debug(`before_reset hook unavailable on this runtime: ${String(error)}`);
12103
+ log9.debug(`before_reset hook unavailable on this runtime: ${String(error)}`);
11746
12104
  }
11747
12105
  if (!passiveMode && sdkCaps.hasBeforePromptBuild) {
11748
12106
  api.on(
11749
12107
  "session_start",
11750
12108
  async (event, ctx) => {
11751
12109
  const sessionKey = event.sessionKey ?? "default";
11752
- log8.debug(`session_start: ${sessionKey}`);
12110
+ log9.debug(`session_start: ${sessionKey}`);
11753
12111
  try {
11754
12112
  await orchestrator.maybeRunFileHygiene().catch(() => void 0);
11755
12113
  } catch (err) {
11756
- log8.debug(`session_start file hygiene failed: ${err}`);
12114
+ log9.debug(`session_start file hygiene failed: ${err}`);
11757
12115
  }
11758
12116
  void queueOpenClawFlushPlanProcessing(
11759
12117
  "session_start",
@@ -11766,7 +12124,7 @@ Keep the reflection grounded in the evidence below.
11766
12124
  "session_end",
11767
12125
  async (event, ctx) => {
11768
12126
  const sessionKey = event.sessionKey ?? ctx.sessionKey ?? "default";
11769
- log8.debug(`session_end: ${sessionKey}`);
12127
+ log9.debug(`session_end: ${sessionKey}`);
11770
12128
  const sessionIdentity = resolveSessionIdentity2(sessionKey, event, ctx);
11771
12129
  const rememberedThreadId = sessionIdentity.providerThreadId ?? resolveStoredCodexThreadId2(sessionKey);
11772
12130
  const bufferKeys = await resolveBeforeResetBufferKeys(sessionKey, {
@@ -11784,7 +12142,7 @@ Keep the reflection grounded in the evidence below.
11784
12142
  });
11785
12143
  } catch (error) {
11786
12144
  flushFailed = true;
11787
- log8.warn(
12145
+ log9.warn(
11788
12146
  `session_end flush failed for ${bufferKey}: ${String(error)}`
11789
12147
  );
11790
12148
  }
@@ -11809,7 +12167,7 @@ Keep the reflection grounded in the evidence below.
11809
12167
  "before_tool_call",
11810
12168
  async (event, _ctx) => {
11811
12169
  if (event.toolName) {
11812
- log8.debug(`before_tool_call: ${event.toolName}`);
12170
+ log9.debug(`before_tool_call: ${event.toolName}`);
11813
12171
  }
11814
12172
  }
11815
12173
  );
@@ -11817,7 +12175,7 @@ Keep the reflection grounded in the evidence below.
11817
12175
  "after_tool_call",
11818
12176
  async (event, _ctx) => {
11819
12177
  if (event.toolName) {
11820
- log8.debug(
12178
+ log9.debug(
11821
12179
  `after_tool_call: ${event.toolName} (${event.durationMs ?? "?"}ms)`
11822
12180
  );
11823
12181
  }
@@ -11828,7 +12186,7 @@ Keep the reflection grounded in the evidence below.
11828
12186
  async (event, ctx) => {
11829
12187
  const sessionKey = ctx?.sessionKey ?? "default";
11830
12188
  if (event.tokenUsage) {
11831
- log8.debug(
12189
+ log9.debug(
11832
12190
  `llm_output: model=${event.model ?? "?"}, tokens=${event.tokenUsage.input ?? 0}/${event.tokenUsage.output ?? 0}, ${event.durationMs ?? "?"}ms, session=${sessionKey}`
11833
12191
  );
11834
12192
  }
@@ -11837,7 +12195,7 @@ Keep the reflection grounded in the evidence below.
11837
12195
  api.on(
11838
12196
  "subagent_spawned",
11839
12197
  async (event, _ctx) => {
11840
- log8.debug(
12198
+ log9.debug(
11841
12199
  `subagent_spawned: ${event.subagentId ?? "?"} purpose=${event.purpose ?? "?"} parent=${event.parentSessionKey ?? "?"}`
11842
12200
  );
11843
12201
  }
@@ -11845,7 +12203,7 @@ Keep the reflection grounded in the evidence below.
11845
12203
  api.on(
11846
12204
  "subagent_ended",
11847
12205
  async (event, _ctx) => {
11848
- log8.debug(
12206
+ log9.debug(
11849
12207
  `subagent_ended: ${event.subagentId ?? "?"} success=${event.success ?? "?"} ${event.durationMs ?? "?"}ms`
11850
12208
  );
11851
12209
  }
@@ -11854,8 +12212,8 @@ Keep the reflection grounded in the evidence below.
11854
12212
  }
11855
12213
  async function ensureHourlySummaryCron(api2) {
11856
12214
  const jobId = "engram-hourly-summary";
11857
- const cronFilePath = path11.join(
11858
- os.homedir(),
12215
+ const cronFilePath = path13.join(
12216
+ os2.homedir(),
11859
12217
  ".openclaw",
11860
12218
  "cron",
11861
12219
  "jobs.json"
@@ -11863,7 +12221,7 @@ Keep the reflection grounded in the evidence below.
11863
12221
  try {
11864
12222
  const loadedJobs = await loadHourlySummaryCronJobsData(cronFilePath);
11865
12223
  if (loadedJobs.status === "invalid") {
11866
- log8.error(
12224
+ log9.error(
11867
12225
  `hourly summary cron auto-registration skipped: existing jobs file is invalid at ${cronFilePath}`,
11868
12226
  loadedJobs.error
11869
12227
  );
@@ -11878,7 +12236,7 @@ Keep the reflection grounded in the evidence below.
11878
12236
  { nowMs: Date.now() }
11879
12237
  );
11880
12238
  if (!changed) {
11881
- log8.debug("hourly summary cron job already up to date");
12239
+ log9.debug("hourly summary cron job already up to date");
11882
12240
  return;
11883
12241
  }
11884
12242
  jobsData.jobs[existingIndex] = job;
@@ -11886,7 +12244,7 @@ Keep the reflection grounded in the evidence below.
11886
12244
  cronFilePath,
11887
12245
  JSON.stringify(jobsData, null, 2)
11888
12246
  );
11889
- log8.info("reconciled hourly summary cron job routing to configured task model");
12247
+ log9.info("reconciled hourly summary cron job routing to configured task model");
11890
12248
  return;
11891
12249
  }
11892
12250
  const randomMinute = Math.floor(Math.random() * 59) + 1;
@@ -11900,9 +12258,9 @@ Keep the reflection grounded in the evidence below.
11900
12258
  cronFilePath,
11901
12259
  JSON.stringify(jobsData, null, 2)
11902
12260
  );
11903
- log8.info("auto-registered hourly summary cron job");
12261
+ log9.info("auto-registered hourly summary cron job");
11904
12262
  } catch (err) {
11905
- log8.error("failed to auto-register hourly summary cron job:", err);
12263
+ log9.error("failed to auto-register hourly summary cron job:", err);
11906
12264
  }
11907
12265
  }
11908
12266
  if (typeof api.registerMemoryCorpusSupplement === "function") {
@@ -11913,32 +12271,32 @@ Keep the reflection grounded in the evidence below.
11913
12271
  };
11914
12272
  const normalizeCorpusPath = (value) => value.trim().replace(/\\/g, "/").replace(/^\.\//, "");
11915
12273
  const pathIsInside = (root, candidate) => {
11916
- const relative = path11.relative(root, candidate);
11917
- return relative === "" || !relative.startsWith("..") && !path11.isAbsolute(relative);
12274
+ const relative = path13.relative(root, candidate);
12275
+ return relative === "" || !relative.startsWith("..") && !path13.isAbsolute(relative);
11918
12276
  };
11919
12277
  const corpusPathCandidates = (rawPath, storageDir) => {
11920
12278
  const candidates = /* @__PURE__ */ new Set();
11921
12279
  const trimmed = rawPath.trim();
11922
12280
  if (!trimmed) return [];
11923
12281
  candidates.add(normalizeCorpusPath(trimmed));
11924
- if (path11.isAbsolute(trimmed)) {
11925
- const absolutePath = path11.resolve(trimmed);
11926
- const absoluteStorageDir = path11.resolve(storageDir);
12282
+ if (path13.isAbsolute(trimmed)) {
12283
+ const absolutePath = path13.resolve(trimmed);
12284
+ const absoluteStorageDir = path13.resolve(storageDir);
11927
12285
  if (pathIsInside(absoluteStorageDir, absolutePath)) {
11928
- candidates.add(normalizeCorpusPath(path11.relative(absoluteStorageDir, absolutePath)));
12286
+ candidates.add(normalizeCorpusPath(path13.relative(absoluteStorageDir, absolutePath)));
11929
12287
  }
11930
12288
  }
11931
- candidates.add(path11.basename(trimmed));
12289
+ candidates.add(path13.basename(trimmed));
11932
12290
  return [...candidates].filter((candidate) => candidate.length > 0);
11933
12291
  };
11934
12292
  const displayCorpusPath = (rawPath, storageDir) => {
11935
12293
  const trimmed = rawPath.trim();
11936
12294
  if (!trimmed) return "";
11937
- if (path11.isAbsolute(trimmed)) {
11938
- const absolutePath = path11.resolve(trimmed);
11939
- const absoluteStorageDir = path11.resolve(storageDir);
12295
+ if (path13.isAbsolute(trimmed)) {
12296
+ const absolutePath = path13.resolve(trimmed);
12297
+ const absoluteStorageDir = path13.resolve(storageDir);
11940
12298
  if (pathIsInside(absoluteStorageDir, absolutePath)) {
11941
- return normalizeCorpusPath(path11.relative(absoluteStorageDir, absolutePath));
12299
+ return normalizeCorpusPath(path13.relative(absoluteStorageDir, absolutePath));
11942
12300
  }
11943
12301
  }
11944
12302
  return normalizeCorpusPath(trimmed);
@@ -12012,7 +12370,7 @@ Keep the reflection grounded in the evidence below.
12012
12370
  };
12013
12371
  });
12014
12372
  } catch (err) {
12015
- log8.warn(`memory corpus search failed: ${err}`);
12373
+ log9.warn(`memory corpus search failed: ${err}`);
12016
12374
  throw corpusBackendError("search", err);
12017
12375
  }
12018
12376
  },
@@ -12048,30 +12406,31 @@ Keep the reflection grounded in the evidence below.
12048
12406
  updatedAt: memory.frontmatter.updated
12049
12407
  };
12050
12408
  } catch (err) {
12051
- log8.warn(`memory corpus get failed: ${err}`);
12409
+ log9.warn(`memory corpus get failed: ${err}`);
12052
12410
  throw corpusBackendError("get", err);
12053
12411
  }
12054
12412
  }
12055
12413
  };
12056
12414
  api.registerMemoryCorpusSupplement(remnicCorpusSupplement);
12057
12415
  }
12058
- if (cfg.openclawToolsEnabled !== false && typeof api.registerTool === "function") {
12059
- api.registerTool(
12060
- buildMemorySearchTool(orchestrator, {
12061
- snippetMaxChars: cfg.openclawToolSnippetMaxChars
12062
- })
12063
- );
12064
- api.registerTool(buildMemoryGetTool(orchestrator));
12065
- }
12416
+ const adaptersEnabled = cfg.openclawToolsEnabled !== false;
12417
+ const ownedToolNames = registerEmbeddedTools(api, {
12418
+ enabled: adaptersEnabled,
12419
+ passive: passiveMode,
12420
+ tools: adaptersEnabled ? [
12421
+ buildMemorySearchTool(orchestrator, { snippetMaxChars: cfg.openclawToolSnippetMaxChars }),
12422
+ buildMemoryGetTool(orchestrator)
12423
+ ] : [buildLegacyMemorySearchToolForPublicShape(orchestrator)]
12424
+ });
12066
12425
  const commandApi = api;
12067
- if (!passiveMode && cfg.commandsListEnabled && cfg.sessionTogglesEnabled !== false) {
12426
+ if (!discoveryPass && !passiveMode && cfg.commandsListEnabled && cfg.sessionTogglesEnabled !== false) {
12068
12427
  if (typeof commandApi.registerCommand === "function" && !globalThis[SESSION_COMMANDS_REGISTERED_GUARD]) {
12069
12428
  globalThis[SESSION_COMMANDS_REGISTERED_GUARD] = true;
12070
12429
  for (const descriptor of sessionCommandDescriptors) {
12071
12430
  commandApi.registerCommand(descriptor);
12072
12431
  }
12073
12432
  } else if (typeof commandApi.registerCommand !== "function") {
12074
- log8.debug(
12433
+ log9.debug(
12075
12434
  "registerCommand unavailable on this runtime; skipping Remnic session command descriptors because commands.list is a gateway RPC surface, not a typed plugin hook"
12076
12435
  );
12077
12436
  }
@@ -12080,7 +12439,9 @@ Keep the reflection grounded in the evidence below.
12080
12439
  api,
12081
12440
  orchestrator,
12082
12441
  // Host-native, model-inaccessible origin for shared-context tool writes.
12083
- getOpenClawRuntimeAgentId(api)
12442
+ getOpenClawRuntimeAgentId(api),
12443
+ // Names the shared ownership record already carries.
12444
+ ownedToolNames
12084
12445
  );
12085
12446
  if (orchestrator.lcmEngine?.enabled) {
12086
12447
  registerLcmTools(
@@ -12088,7 +12449,7 @@ Keep the reflection grounded in the evidence below.
12088
12449
  orchestrator.lcmEngine
12089
12450
  );
12090
12451
  }
12091
- if (!globalThis[CLI_REGISTERED_GUARD]) {
12452
+ if (!discoveryPass && !globalThis[CLI_REGISTERED_GUARD]) {
12092
12453
  globalThis[CLI_REGISTERED_GUARD] = true;
12093
12454
  registerCli(
12094
12455
  api,
@@ -12110,7 +12471,7 @@ Keep the reflection grounded in the evidence below.
12110
12471
  if (globalThis[keys.SERVICE_STARTED]) return;
12111
12472
  }
12112
12473
  if (globalThis[keys.SERVICE_STARTED]) {
12113
- log8.debug(
12474
+ log9.debug(
12114
12475
  `${serviceId}: service.start() called again \u2014 skipping duplicate init`
12115
12476
  );
12116
12477
  return;
@@ -12123,10 +12484,10 @@ Keep the reflection grounded in the evidence below.
12123
12484
  globalThis[keys.ORCHESTRATOR] = orchestrator;
12124
12485
  const initPromise = (async () => {
12125
12486
  try {
12126
- log8.info("initializing engram memory system...");
12487
+ log9.info("initializing engram memory system...");
12127
12488
  await orchestrator.initialize();
12128
12489
  if (!didCountStart) return;
12129
- activeOpikExporter = createOpikExporter({}, log8);
12490
+ activeOpikExporter = createOpikExporter({}, log9);
12130
12491
  if (activeOpikExporter) activeOpikExporter.subscribe();
12131
12492
  if (orchestrator.config.transcriptEnabled) {
12132
12493
  await orchestrator.transcript.cleanup(
@@ -12138,7 +12499,7 @@ Keep the reflection grounded in the evidence below.
12138
12499
  await ensureHourlySummaryCron(api);
12139
12500
  if (!didCountStart) return;
12140
12501
  } else if (orchestrator.config.hourlySummariesEnabled) {
12141
- log8.info(
12502
+ log9.info(
12142
12503
  "hourly summaries enabled; cron auto-register is disabled. To schedule summaries, create an isolated/agentTurn cron job that calls `memory_summarize_hourly`."
12143
12504
  );
12144
12505
  }
@@ -12154,7 +12515,7 @@ Keep the reflection grounded in the evidence below.
12154
12515
  resolveDreamJournalPath(),
12155
12516
  () => {
12156
12517
  void queueDreamSurfaceSync().catch((error) => {
12157
- log8.debug(`dream surface watch sync failed: ${String(error)}`);
12518
+ log9.debug(`dream surface watch sync failed: ${String(error)}`);
12158
12519
  });
12159
12520
  }
12160
12521
  );
@@ -12174,7 +12535,7 @@ Keep the reflection grounded in the evidence below.
12174
12535
  resolveHeartbeatJournalPath(),
12175
12536
  () => {
12176
12537
  void queueHeartbeatSurfaceSync().catch((error) => {
12177
- log8.debug(`heartbeat surface watch sync failed: ${String(error)}`);
12538
+ log9.debug(`heartbeat surface watch sync failed: ${String(error)}`);
12178
12539
  });
12179
12540
  }
12180
12541
  );
@@ -12195,17 +12556,17 @@ Keep the reflection grounded in the evidence below.
12195
12556
  }
12196
12557
  try {
12197
12558
  const status = await accessHttpServer.start();
12198
- log8.info(
12559
+ log9.info(
12199
12560
  `engram access HTTP ready at http://${status.host}:${status.port}`
12200
12561
  );
12201
12562
  } catch (err) {
12202
- log8.error("failed to start engram access HTTP server", err);
12563
+ log9.error("failed to start engram access HTTP server", err);
12203
12564
  }
12204
12565
  }
12205
12566
  if (!didCountStart) return;
12206
12567
  globalThis[keys.SERVICE_STARTED] = true;
12207
- log8.info("engram memory system ready");
12208
- log8.info(
12568
+ log9.info("engram memory system ready");
12569
+ log9.info(
12209
12570
  `gateway_start fired \u2014 Remnic memory plugin is active (id=${pluginDefinition.id}, memoryDir=${cfg.memoryDir})`
12210
12571
  );
12211
12572
  void queueOpenClawFlushPlanProcessing(
@@ -12279,7 +12640,7 @@ Keep the reflection grounded in the evidence below.
12279
12640
  try {
12280
12641
  activeOpikExporter?.unsubscribe();
12281
12642
  } catch (err) {
12282
- log8.debug(`engram opik exporter unsubscribe failed: ${err}`);
12643
+ log9.debug(`engram opik exporter unsubscribe failed: ${err}`);
12283
12644
  }
12284
12645
  activeOpikExporter = null;
12285
12646
  const finishCodex = beginCodexSubscriptionShutdown(getCodexSubscriptionRunnerForOwner(cfg));
@@ -12287,7 +12648,7 @@ Keep the reflection grounded in the evidence below.
12287
12648
  try {
12288
12649
  await accessHttpServer.stop();
12289
12650
  } catch (err) {
12290
- log8.debug(`engram access HTTP stop failed: ${err}`);
12651
+ log9.debug(`engram access HTTP stop failed: ${err}`);
12291
12652
  }
12292
12653
  stopDreamWatcher?.();
12293
12654
  stopDreamWatcher = null;
@@ -12298,7 +12659,7 @@ Keep the reflection grounded in the evidence below.
12298
12659
  try {
12299
12660
  await orchestrator.destroy();
12300
12661
  } catch (err) {
12301
- log8.debug(`engram orchestrator destroy on stop failed: ${err}`);
12662
+ log9.debug(`engram orchestrator destroy on stop failed: ${err}`);
12302
12663
  }
12303
12664
  } finally {
12304
12665
  finishCodex();
@@ -12324,7 +12685,7 @@ Keep the reflection grounded in the evidence below.
12324
12685
  if (!secondaryTookOver) {
12325
12686
  globalThis[keys.SERVICE_STARTED] = false;
12326
12687
  }
12327
- log8.info("stopped");
12688
+ log9.info("stopped");
12328
12689
  }
12329
12690
  });
12330
12691
  }
@@ -12460,9 +12821,9 @@ function truncateMetadataValue(value, maxChars) {
12460
12821
  return value.length <= maxChars ? value : value.slice(0, maxChars);
12461
12822
  }
12462
12823
  async function resolveFlushPlanProcessingChainKey(workspaceRoot) {
12463
- const lexicalRoot = path11.resolve(workspaceRoot);
12824
+ const lexicalRoot = path13.resolve(workspaceRoot);
12464
12825
  try {
12465
- return path11.resolve(await realPathLater(lexicalRoot));
12826
+ return path13.resolve(await realPathLater(lexicalRoot));
12466
12827
  } catch {
12467
12828
  return lexicalRoot;
12468
12829
  }