@remnic/plugin-openclaw 1.0.6 → 1.0.7

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/README.md CHANGED
@@ -38,6 +38,42 @@ Then restart the gateway:
38
38
  launchctl kickstart -k gui/501/ai.openclaw.gateway
39
39
  ```
40
40
 
41
+ ## Benchmarking The OpenClaw Chain
42
+
43
+ The benchmark CLI can now exercise the real OpenClaw-backed answer path instead
44
+ of only the stripped retrieval harness. Use the `openclaw-chain` runtime
45
+ profile to load the Remnic plugin config from `openclaw.json`, route answer
46
+ generation through the configured gateway chain, and optionally attach a
47
+ provider-backed judge:
48
+
49
+ ```bash
50
+ remnic bench run longmemeval \
51
+ --runtime-profile openclaw-chain \
52
+ --openclaw-config ~/.openclaw/openclaw.json \
53
+ --gateway-agent-id memory-primary
54
+
55
+ remnic bench run longmemeval \
56
+ --runtime-profile openclaw-chain \
57
+ --openclaw-config ~/.openclaw/openclaw.json \
58
+ --gateway-agent-id memory-primary \
59
+ --judge-provider openai \
60
+ --judge-model gpt-5.4-mini
61
+ ```
62
+
63
+ To compare the stripped harness, direct Remnic runtime, and OpenClaw chain in a
64
+ single pass, run a profile matrix:
65
+
66
+ ```bash
67
+ remnic bench run longmemeval \
68
+ --matrix baseline,real,openclaw-chain \
69
+ --openclaw-config ~/.openclaw/openclaw.json \
70
+ --remnic-config ~/.config/remnic/config.json
71
+ ```
72
+
73
+ Each stored result records its `runtimeProfile`, provider metadata, and the
74
+ resolved Remnic config so benchmark comparisons can distinguish retrieval-only
75
+ runs from real runtime and OpenClaw chain runs.
76
+
41
77
  ## What it does
42
78
 
43
79
  This plugin hooks into the OpenClaw gateway lifecycle:
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  FallbackLlmClient
3
- } from "./chunk-3SA5F4WT.js";
3
+ } from "./chunk-NXLHSCLU.js";
4
+ import "./chunk-3A5ELHTT.js";
4
5
  import {
5
6
  listJsonFiles
6
7
  } from "./chunk-5LE4HTVL.js";
@@ -12,7 +12,8 @@ import {
12
12
  } from "./chunk-YHH3SXKD.js";
13
13
  import {
14
14
  FallbackLlmClient
15
- } from "./chunk-3SA5F4WT.js";
15
+ } from "./chunk-NXLHSCLU.js";
16
+ import "./chunk-3A5ELHTT.js";
16
17
  import {
17
18
  listJsonFiles,
18
19
  readJsonFile
@@ -0,0 +1,61 @@
1
+ // ../remnic-core/src/json-extract.ts
2
+ function stripCodeFences(text) {
3
+ return text.replace(/```(?:json)?\s*([\s\S]*?)```/gi, (_m, inner) => String(inner).trim());
4
+ }
5
+ function extractJsonCandidates(text) {
6
+ const trimmed = text.trim();
7
+ const cleaned = stripCodeFences(trimmed);
8
+ const candidates = [];
9
+ if (cleaned.length > 0) candidates.push(cleaned);
10
+ candidates.push(...scanBalancedJsonBlocks(cleaned));
11
+ const objMatch = cleaned.match(/\{[\s\S]*\}/);
12
+ if (objMatch) candidates.push(objMatch[0]);
13
+ const seen = /* @__PURE__ */ new Set();
14
+ return candidates.map((c) => c.trim()).filter((c) => c.length > 0).filter((c) => {
15
+ if (seen.has(c)) return false;
16
+ seen.add(c);
17
+ return true;
18
+ });
19
+ }
20
+ function scanBalancedJsonBlocks(text) {
21
+ const out = [];
22
+ const opens = /* @__PURE__ */ new Set(["{", "["]);
23
+ const closes = { "{": "}", "[": "]" };
24
+ for (let i = 0; i < text.length; i++) {
25
+ const start = text[i];
26
+ if (!opens.has(start)) continue;
27
+ const expectedClose = closes[start];
28
+ let depth = 0;
29
+ let inString = false;
30
+ let escape = false;
31
+ for (let j = i; j < text.length; j++) {
32
+ const ch = text[j];
33
+ if (inString) {
34
+ if (escape) {
35
+ escape = false;
36
+ } else if (ch === "\\") {
37
+ escape = true;
38
+ } else if (ch === '"') {
39
+ inString = false;
40
+ }
41
+ continue;
42
+ }
43
+ if (ch === '"') {
44
+ inString = true;
45
+ continue;
46
+ }
47
+ if (ch === start) depth++;
48
+ if (ch === expectedClose) depth--;
49
+ if (depth === 0) {
50
+ out.push(text.slice(i, j + 1).trim());
51
+ i = j;
52
+ break;
53
+ }
54
+ }
55
+ }
56
+ return out;
57
+ }
58
+
59
+ export {
60
+ extractJsonCandidates
61
+ };
@@ -0,0 +1,136 @@
1
+ // ../remnic-core/src/contradiction/contradiction-review.ts
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import { createHash } from "crypto";
5
+ function computePairId(memoryIdA, memoryIdB) {
6
+ const sorted = [memoryIdA, memoryIdB].sort();
7
+ return createHash("sha256").update(sorted.join("::")).digest("hex").slice(0, 24);
8
+ }
9
+ function reviewDir(memoryDir) {
10
+ return path.join(memoryDir, ".review", "contradictions");
11
+ }
12
+ function pairPath(memoryDir, pairId) {
13
+ if (pairId.includes("/") || pairId.includes("\\") || pairId.includes("..")) {
14
+ throw new Error(`Invalid pairId: ${pairId}`);
15
+ }
16
+ return path.join(reviewDir(memoryDir), `${pairId}.json`);
17
+ }
18
+ function ensureDir(memoryDir) {
19
+ const dir = reviewDir(memoryDir);
20
+ if (!fs.existsSync(dir)) {
21
+ fs.mkdirSync(dir, { recursive: true });
22
+ }
23
+ }
24
+ function writePair(memoryDir, pair) {
25
+ ensureDir(memoryDir);
26
+ const pairId = computePairId(pair.memoryIds[0], pair.memoryIds[1]);
27
+ const existing = readPair(memoryDir, pairId);
28
+ if (existing?.resolution) {
29
+ return existing;
30
+ }
31
+ if (existing && existing.confidence >= pair.confidence) {
32
+ return existing;
33
+ }
34
+ const full = {
35
+ ...pair,
36
+ pairId,
37
+ lastReviewedAt: existing?.lastReviewedAt,
38
+ resolution: existing?.resolution
39
+ };
40
+ const filePath = pairPath(memoryDir, pairId);
41
+ const tmpPath = `${filePath}.tmp`;
42
+ fs.writeFileSync(tmpPath, JSON.stringify(full, null, 2), "utf-8");
43
+ fs.renameSync(tmpPath, filePath);
44
+ return full;
45
+ }
46
+ function writePairs(memoryDir, pairs) {
47
+ const seen = /* @__PURE__ */ new Set();
48
+ const results = [];
49
+ for (const pair of pairs) {
50
+ const key = computePairId(pair.memoryIds[0], pair.memoryIds[1]);
51
+ if (seen.has(key)) continue;
52
+ seen.add(key);
53
+ results.push(writePair(memoryDir, pair));
54
+ }
55
+ return results;
56
+ }
57
+ function readPair(memoryDir, pairId) {
58
+ const filePath = pairPath(memoryDir, pairId);
59
+ try {
60
+ const raw = fs.readFileSync(filePath, "utf-8");
61
+ const parsed = JSON.parse(raw);
62
+ if (typeof parsed === "object" && parsed !== null && Array.isArray(parsed.memoryIds)) {
63
+ return parsed;
64
+ }
65
+ return null;
66
+ } catch {
67
+ return null;
68
+ }
69
+ }
70
+ function listPairs(memoryDir, options) {
71
+ const startTime = Date.now();
72
+ const dir = reviewDir(memoryDir);
73
+ const { filter = "all", namespace, limit = 50 } = options ?? {};
74
+ const pairs = [];
75
+ let total = 0;
76
+ if (!fs.existsSync(dir)) {
77
+ return { pairs: [], total: 0, durationMs: Date.now() - startTime };
78
+ }
79
+ for (const entry of fs.readdirSync(dir)) {
80
+ if (!entry.endsWith(".json")) continue;
81
+ try {
82
+ const raw = fs.readFileSync(path.join(dir, entry), "utf-8");
83
+ const pair = JSON.parse(raw);
84
+ if (typeof pair !== "object" || pair === null) continue;
85
+ if (!Array.isArray(pair.memoryIds)) continue;
86
+ if (namespace && pair.namespace !== namespace) continue;
87
+ if (filter === "unresolved") {
88
+ if (pair.resolution) continue;
89
+ if (pair.verdict === "independent") continue;
90
+ } else if (filter !== "all" && pair.verdict !== filter) {
91
+ continue;
92
+ }
93
+ total++;
94
+ if (pairs.length < limit) pairs.push(pair);
95
+ } catch {
96
+ continue;
97
+ }
98
+ }
99
+ return { pairs, total, durationMs: Date.now() - startTime };
100
+ }
101
+ function isCoolingDown(pair, cooldownDays) {
102
+ if (cooldownDays <= 0) return false;
103
+ if (!pair.lastReviewedAt) return false;
104
+ const lastReviewed = new Date(pair.lastReviewedAt).getTime();
105
+ if (!Number.isFinite(lastReviewed)) return false;
106
+ const cooldownMs = cooldownDays * 24 * 60 * 60 * 1e3;
107
+ return Date.now() < lastReviewed + cooldownMs;
108
+ }
109
+ function resolvePair(memoryDir, pairId, verb) {
110
+ const existing = readPair(memoryDir, pairId);
111
+ if (!existing) return null;
112
+ const updated = {
113
+ ...existing,
114
+ lastReviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
115
+ resolution: verb
116
+ };
117
+ const filePath = pairPath(memoryDir, pairId);
118
+ const tmpPath = `${filePath}.tmp`;
119
+ fs.writeFileSync(tmpPath, JSON.stringify(updated, null, 2), "utf-8");
120
+ fs.renameSync(tmpPath, filePath);
121
+ return updated;
122
+ }
123
+ function memoryHashesChanged(_memoryDir, _pair, _getCurrentHash) {
124
+ return false;
125
+ }
126
+
127
+ export {
128
+ computePairId,
129
+ writePair,
130
+ writePairs,
131
+ readPair,
132
+ listPairs,
133
+ isCoolingDown,
134
+ resolvePair,
135
+ memoryHashesChanged
136
+ };
@@ -1,65 +1,10 @@
1
+ import {
2
+ extractJsonCandidates
3
+ } from "./chunk-3A5ELHTT.js";
1
4
  import {
2
5
  log
3
6
  } from "./chunk-UFU5GGGA.js";
4
7
 
5
- // ../remnic-core/src/json-extract.ts
6
- function stripCodeFences(text) {
7
- return text.replace(/```(?:json)?\s*([\s\S]*?)```/gi, (_m, inner) => String(inner).trim());
8
- }
9
- function extractJsonCandidates(text) {
10
- const trimmed = text.trim();
11
- const cleaned = stripCodeFences(trimmed);
12
- const candidates = [];
13
- if (cleaned.length > 0) candidates.push(cleaned);
14
- candidates.push(...scanBalancedJsonBlocks(cleaned));
15
- const objMatch = cleaned.match(/\{[\s\S]*\}/);
16
- if (objMatch) candidates.push(objMatch[0]);
17
- const seen = /* @__PURE__ */ new Set();
18
- return candidates.map((c) => c.trim()).filter((c) => c.length > 0).filter((c) => {
19
- if (seen.has(c)) return false;
20
- seen.add(c);
21
- return true;
22
- });
23
- }
24
- function scanBalancedJsonBlocks(text) {
25
- const out = [];
26
- const opens = /* @__PURE__ */ new Set(["{", "["]);
27
- const closes = { "{": "}", "[": "]" };
28
- for (let i = 0; i < text.length; i++) {
29
- const start = text[i];
30
- if (!opens.has(start)) continue;
31
- const expectedClose = closes[start];
32
- let depth = 0;
33
- let inString = false;
34
- let escape = false;
35
- for (let j = i; j < text.length; j++) {
36
- const ch = text[j];
37
- if (inString) {
38
- if (escape) {
39
- escape = false;
40
- } else if (ch === "\\") {
41
- escape = true;
42
- } else if (ch === '"') {
43
- inString = false;
44
- }
45
- continue;
46
- }
47
- if (ch === '"') {
48
- inString = true;
49
- continue;
50
- }
51
- if (ch === start) depth++;
52
- if (ch === expectedClose) depth--;
53
- if (depth === 0) {
54
- out.push(text.slice(i, j + 1).trim());
55
- i = j;
56
- break;
57
- }
58
- }
59
- }
60
- return out;
61
- }
62
-
63
8
  // ../remnic-core/src/openai-chat-compat.ts
64
9
  function normalizedModel(model) {
65
10
  return model.trim().toLowerCase();
@@ -183,13 +128,25 @@ async function getGatewayResolver() {
183
128
  async function findRuntimeModules() {
184
129
  const { readdirSync } = await import("fs");
185
130
  const { createRequire } = await import("module");
131
+ const { execFileSync } = await import("child_process");
186
132
  const candidates = [];
187
133
  const distDirs = [];
134
+ const pushDistDirs = (entryPath) => {
135
+ const resolvedEntryDir = path.dirname(entryPath);
136
+ const packageRoot = path.basename(resolvedEntryDir) === "dist" ? path.resolve(resolvedEntryDir, "..") : resolvedEntryDir;
137
+ const candidateDistDirs = [
138
+ path.join(packageRoot, "dist"),
139
+ path.join(packageRoot, "..", "dist")
140
+ ];
141
+ for (const candidate of candidateDistDirs) {
142
+ const resolved = path.resolve(candidate);
143
+ if (!distDirs.includes(resolved)) distDirs.push(resolved);
144
+ }
145
+ };
188
146
  try {
189
147
  const req = createRequire(import.meta.url);
190
148
  const openclawMain = req.resolve("openclaw");
191
- const openclawDist = path.join(path.dirname(openclawMain), "..", "dist");
192
- if (openclawDist) distDirs.push(path.resolve(openclawDist));
149
+ pushDistDirs(openclawMain);
193
150
  } catch {
194
151
  }
195
152
  try {
@@ -198,12 +155,22 @@ async function findRuntimeModules() {
198
155
  if (mainScript) {
199
156
  const realScript = realpathSync(mainScript);
200
157
  if (realScript.includes("openclaw")) {
201
- const distDir = path.dirname(realScript);
202
- if (!distDirs.includes(distDir)) distDirs.push(distDir);
158
+ pushDistDirs(realScript);
203
159
  }
204
160
  }
205
161
  } catch {
206
162
  }
163
+ try {
164
+ const openclawBin = execFileSync("which", ["openclaw"], {
165
+ encoding: "utf8",
166
+ stdio: ["ignore", "pipe", "ignore"]
167
+ }).trim();
168
+ if (openclawBin) {
169
+ const { realpathSync } = await import("fs");
170
+ pushDistDirs(realpathSync(openclawBin));
171
+ }
172
+ } catch {
173
+ }
207
174
  for (const dir of distDirs) {
208
175
  try {
209
176
  const files = readdirSync(dir);
@@ -218,7 +185,10 @@ async function findRuntimeModules() {
218
185
  return candidates;
219
186
  }
220
187
  async function resolveProviderApiKey(providerId, apiKeyValue, gatewayConfig, agentDir) {
221
- const cacheKey = `provider:${providerId}`;
188
+ const resolvedAgentDir = path.resolve(
189
+ agentDir ?? path.join(os2.homedir(), ".openclaw", "agents", "main", "agent")
190
+ );
191
+ const cacheKey = `provider:${providerId}:agentDir:${resolvedAgentDir}`;
222
192
  if (resolvedCache.has(cacheKey)) {
223
193
  return resolvedCache.get(cacheKey);
224
194
  }
@@ -234,7 +204,6 @@ async function resolveProviderApiKey(providerId, apiKeyValue, gatewayConfig, age
234
204
  const resolver = await getGatewayResolver();
235
205
  if (resolver) {
236
206
  try {
237
- const resolvedAgentDir = agentDir ?? path.join(os2.homedir(), ".openclaw", "agents", "main", "agent");
238
207
  const auth = await resolver({ provider: providerId, cfg: gatewayConfig, agentDir: resolvedAgentDir });
239
208
  if (auth?.apiKey) {
240
209
  resolved = auth.apiKey;
@@ -310,8 +279,10 @@ function loadModelsJsonProviders() {
310
279
  // ../remnic-core/src/fallback-llm.ts
311
280
  var FallbackLlmClient = class {
312
281
  gatewayConfig;
313
- constructor(gatewayConfig) {
282
+ runtimeContext;
283
+ constructor(gatewayConfig, runtimeContext = {}) {
314
284
  this.gatewayConfig = gatewayConfig;
285
+ this.runtimeContext = runtimeContext;
315
286
  }
316
287
  /**
317
288
  * Check if fallback is available (gateway config has at least one model).
@@ -513,6 +484,14 @@ var FallbackLlmClient = class {
513
484
  if (model.providerConfig.api === "anthropic-messages") {
514
485
  return await this.callAnthropic(effectiveConfig, model.modelId, messages, options);
515
486
  }
487
+ if (model.providerConfig.api === "openai-responses" || model.providerConfig.api === "openai-codex-responses" || model.providerConfig.api === "azure-openai-responses") {
488
+ return await this.callOpenAIResponses(
489
+ effectiveConfig,
490
+ model.modelId,
491
+ messages,
492
+ options
493
+ );
494
+ }
516
495
  return await this.callOpenAI(
517
496
  effectiveConfig,
518
497
  model.modelId,
@@ -537,7 +516,8 @@ var FallbackLlmClient = class {
537
516
  api: model.providerConfig.api,
538
517
  baseUrl: model.providerConfig.baseUrl
539
518
  },
540
- cfg: this.gatewayConfig
519
+ cfg: this.gatewayConfig,
520
+ workspaceDir: this.runtimeContext.workspaceDir
541
521
  });
542
522
  if (result?.apiKey || result?.baseUrl) {
543
523
  log.debug(
@@ -557,7 +537,12 @@ var FallbackLlmClient = class {
557
537
  * secret refs, etc.). Used as fallback when gateway runtime auth isn't available.
558
538
  */
559
539
  async resolveFallbackApiKey(model) {
560
- return resolveProviderApiKey(model.providerId, model.providerConfig.apiKey, this.gatewayConfig);
540
+ return resolveProviderApiKey(
541
+ model.providerId,
542
+ model.providerConfig.apiKey,
543
+ this.gatewayConfig,
544
+ this.runtimeContext.agentDir
545
+ );
561
546
  }
562
547
  /**
563
548
  * Call OpenAI-compatible API.
@@ -605,11 +590,65 @@ var FallbackLlmClient = class {
605
590
  } : void 0
606
591
  };
607
592
  }
593
+ /**
594
+ * Call an OpenAI-compatible Responses API.
595
+ */
596
+ async callOpenAIResponses(config, modelId, messages, options) {
597
+ const base = config.baseUrl.replace(/\/$/, "");
598
+ const url = base.endsWith("/v1") ? `${base}/responses` : `${base}/v1/responses`;
599
+ const headers = {
600
+ "Content-Type": "application/json",
601
+ ...config.headers
602
+ };
603
+ if (config.apiKey && typeof config.apiKey === "string" && config.authHeader !== false) {
604
+ headers["Authorization"] = `Bearer ${config.apiKey}`;
605
+ }
606
+ const instructions = messages.filter((message) => message.role === "system").map((message) => message.content).join("\n\n").trim();
607
+ const input = messages.filter((message) => message.role !== "system").map((message) => ({
608
+ role: message.role,
609
+ content: [{
610
+ type: message.role === "assistant" ? "output_text" : "input_text",
611
+ text: message.content
612
+ }]
613
+ }));
614
+ const body = {
615
+ model: modelId,
616
+ input,
617
+ max_output_tokens: Math.max(0, Math.floor(options.maxTokens ?? 4096)),
618
+ temperature: options.temperature ?? 0.3
619
+ };
620
+ if (instructions.length > 0) {
621
+ body.instructions = instructions;
622
+ }
623
+ const response = await fetch(url, {
624
+ method: "POST",
625
+ headers,
626
+ body: JSON.stringify(body)
627
+ });
628
+ if (!response.ok) {
629
+ const error = await response.text();
630
+ throw new Error(`OpenAI Responses API error: ${response.status} ${error}`);
631
+ }
632
+ const data = await response.json();
633
+ const outputText = extractResponsesOutputText(data);
634
+ if (!outputText) {
635
+ throw new Error("Empty response from OpenAI Responses API");
636
+ }
637
+ return {
638
+ content: outputText,
639
+ usage: data.usage ? {
640
+ inputTokens: data.usage.input_tokens,
641
+ outputTokens: data.usage.output_tokens,
642
+ totalTokens: data.usage.total_tokens
643
+ } : void 0
644
+ };
645
+ }
608
646
  /**
609
647
  * Call Anthropic Messages API.
610
648
  */
611
649
  async callAnthropic(config, modelId, messages, options) {
612
- const url = `${config.baseUrl.replace(/\/$/, "")}/messages`;
650
+ const base = config.baseUrl.replace(/\/$/, "");
651
+ const url = base.endsWith("/v1") ? `${base}/messages` : `${base}/v1/messages`;
613
652
  const headers = {
614
653
  "Content-Type": "application/json",
615
654
  "anthropic-version": "2023-06-01",
@@ -657,12 +696,29 @@ var FallbackLlmClient = class {
657
696
  };
658
697
  }
659
698
  };
699
+ function extractResponsesOutputText(data) {
700
+ if (typeof data.output_text === "string" && data.output_text.trim().length > 0) {
701
+ return data.output_text;
702
+ }
703
+ const chunks = [];
704
+ for (const item of data.output ?? []) {
705
+ if (typeof item.text === "string" && item.text.trim().length > 0) {
706
+ chunks.push(item.text);
707
+ }
708
+ for (const part of item.content ?? []) {
709
+ if ((part.type === "output_text" || part.type === "text") && typeof part.text === "string" && part.text.trim().length > 0) {
710
+ chunks.push(part.text);
711
+ }
712
+ }
713
+ }
714
+ const joined = chunks.join("\n").trim();
715
+ return joined.length > 0 ? joined : null;
716
+ }
660
717
 
661
718
  export {
662
719
  readEnvVar,
663
720
  resolveHomeDir,
664
721
  mergeEnv,
665
- extractJsonCandidates,
666
722
  shouldAssumeOpenAiChatCompletions,
667
723
  buildChatCompletionTokenLimit,
668
724
  FallbackLlmClient
@@ -4881,7 +4881,7 @@ var CompoundingEngine = class {
4881
4881
  let promotionCandidates = this.config.compoundingSemanticEnabled ? this.derivePromotionCandidates(outcomeSummary, mistakes.registry, rubrics) : [];
4882
4882
  if (this.config.cmcConsolidationEnabled) {
4883
4883
  try {
4884
- const { deriveCausalPromotionCandidates, materializeAfterCausalConsolidation } = await import("./causal-consolidation-EBLROS42.js");
4884
+ const { deriveCausalPromotionCandidates, materializeAfterCausalConsolidation } = await import("./causal-consolidation-S6M7UTZG.js");
4885
4885
  const causalCandidates = await deriveCausalPromotionCandidates({
4886
4886
  memoryDir: this.config.memoryDir,
4887
4887
  causalTrajectoryStoreDir: this.config.causalTrajectoryStoreDir,
@@ -4913,7 +4913,7 @@ var CompoundingEngine = class {
4913
4913
  }
4914
4914
  if (this.config.calibrationEnabled) {
4915
4915
  try {
4916
- const { runCalibrationConsolidation } = await import("./calibration-3JHF25QT.js");
4916
+ const { runCalibrationConsolidation } = await import("./calibration-BAC7KNKR.js");
4917
4917
  const calRules = await runCalibrationConsolidation({
4918
4918
  memoryDir: this.config.memoryDir,
4919
4919
  gatewayConfig: this.config.gatewayConfig,
@@ -0,0 +1,21 @@
1
+ import {
2
+ computePairId,
3
+ isCoolingDown,
4
+ listPairs,
5
+ memoryHashesChanged,
6
+ readPair,
7
+ resolvePair,
8
+ writePair,
9
+ writePairs
10
+ } from "./chunk-DIZW6H5J.js";
11
+ import "./chunk-MLKGABMK.js";
12
+ export {
13
+ computePairId,
14
+ isCoolingDown,
15
+ listPairs,
16
+ memoryHashesChanged,
17
+ readPair,
18
+ resolvePair,
19
+ writePair,
20
+ writePairs
21
+ };