@remnic/core 1.0.3 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/dist/abort-error.d.ts +32 -0
  2. package/dist/abort-error.js +11 -0
  3. package/dist/access-cli.js +41 -40
  4. package/dist/access-cli.js.map +1 -1
  5. package/dist/access-http.d.ts +3 -2
  6. package/dist/access-http.js +11 -11
  7. package/dist/access-mcp.d.ts +3 -2
  8. package/dist/access-mcp.js +7 -7
  9. package/dist/access-schema.d.ts +16 -16
  10. package/dist/access-service-HmO1Trrx.d.ts +732 -0
  11. package/dist/access-service.d.ts +11 -658
  12. package/dist/access-service.js +9 -9
  13. package/dist/bootstrap.d.ts +1 -1
  14. package/dist/briefing.js +6 -6
  15. package/dist/calibration.js +3 -3
  16. package/dist/causal-consolidation.js +10 -10
  17. package/dist/{chunk-PMB3WGDL.js → chunk-37UIFYWO.js} +64 -3
  18. package/dist/chunk-37UIFYWO.js.map +1 -0
  19. package/dist/{chunk-X4WESCKA.js → chunk-3QHL5ABG.js} +5 -5
  20. package/dist/{chunk-2VFW5K5U.js → chunk-3SV6CQHO.js} +10 -8
  21. package/dist/chunk-3SV6CQHO.js.map +1 -0
  22. package/dist/{chunk-U2IQTSBY.js → chunk-3WHVNEN7.js} +1 -1
  23. package/dist/chunk-3WHVNEN7.js.map +1 -0
  24. package/dist/{chunk-QKAH5B6E.js → chunk-44ICJRF3.js} +97 -9
  25. package/dist/chunk-44ICJRF3.js.map +1 -0
  26. package/dist/{chunk-S4LX5EBI.js → chunk-47UU5PU2.js} +48 -9
  27. package/dist/chunk-47UU5PU2.js.map +1 -0
  28. package/dist/{chunk-ECKDIK5F.js → chunk-4LACOVZX.js} +2 -2
  29. package/dist/{chunk-AYPYCLR7.js → chunk-6LX5ORAS.js} +2 -2
  30. package/dist/{chunk-GJQPH5G3.js → chunk-6UJ47TVX.js} +2 -2
  31. package/dist/{chunk-KWP7T3DP.js → chunk-7ECD5ATE.js} +2 -2
  32. package/dist/{chunk-74JR4N5J.js → chunk-7WQ6SLIE.js} +2 -2
  33. package/dist/{chunk-7PA4OZEU.js → chunk-BLKTA7MM.js} +6 -14
  34. package/dist/chunk-BLKTA7MM.js.map +1 -0
  35. package/dist/{chunk-XMGSSBFX.js → chunk-DEPL3635.js} +172 -100
  36. package/dist/chunk-DEPL3635.js.map +1 -0
  37. package/dist/{chunk-JROGC36Y.js → chunk-DHHP2Z4X.js} +2 -2
  38. package/dist/{chunk-BKQJBXXX.js → chunk-GGD5W7TB.js} +2 -2
  39. package/dist/chunk-GGD5W7TB.js.map +1 -0
  40. package/dist/{chunk-POMSFKTB.js → chunk-GV6NLQ4X.js} +14 -14
  41. package/dist/{chunk-AAI7JARD.js → chunk-HMDCOMYU.js} +8 -11
  42. package/dist/chunk-HMDCOMYU.js.map +1 -0
  43. package/dist/{chunk-POBPGDWI.js → chunk-ITRLGI2T.js} +2 -2
  44. package/dist/{chunk-MYQWXITD.js → chunk-JIU55F3X.js} +2 -2
  45. package/dist/{chunk-UPMD5XND.js → chunk-JL2PU6AI.js} +16 -5
  46. package/dist/chunk-JL2PU6AI.js.map +1 -0
  47. package/dist/{chunk-OJFGVJS6.js → chunk-MBJHSA7F.js} +65 -9
  48. package/dist/chunk-MBJHSA7F.js.map +1 -0
  49. package/dist/{chunk-V7XCAHIB.js → chunk-MVTHXUBX.js} +46 -23
  50. package/dist/chunk-MVTHXUBX.js.map +1 -0
  51. package/dist/{chunk-BTY5RRRF.js → chunk-N42IWANG.js} +5 -5
  52. package/dist/{chunk-NSB3WSYS.js → chunk-NQEVYWX6.js} +74 -3
  53. package/dist/chunk-NQEVYWX6.js.map +1 -0
  54. package/dist/chunk-OIT5QGG4.js +80 -0
  55. package/dist/chunk-OIT5QGG4.js.map +1 -0
  56. package/dist/chunk-PVGDJXVK.js +21 -0
  57. package/dist/chunk-PVGDJXVK.js.map +1 -0
  58. package/dist/{chunk-RCICHSHL.js → chunk-SYUK3VLY.js} +2 -2
  59. package/dist/{chunk-YFYL2SIJ.js → chunk-WBSAYXVI.js} +127 -39
  60. package/dist/chunk-WBSAYXVI.js.map +1 -0
  61. package/dist/{chunk-KEG4GNGI.js → chunk-XZ2TIKGC.js} +38 -8
  62. package/dist/chunk-XZ2TIKGC.js.map +1 -0
  63. package/dist/chunk-Y4FHOFJ2.js +140 -0
  64. package/dist/chunk-Y4FHOFJ2.js.map +1 -0
  65. package/dist/chunk-YNB73F22.js +137 -0
  66. package/dist/chunk-YNB73F22.js.map +1 -0
  67. package/dist/{chunk-HITJFT7E.js → chunk-ZVBB3T7V.js} +10 -5
  68. package/dist/chunk-ZVBB3T7V.js.map +1 -0
  69. package/dist/{cli-DwIBnp2g.d.ts → cli-BneVIEvh.d.ts} +2 -2
  70. package/dist/cli.d.ts +4 -3
  71. package/dist/cli.js +25 -24
  72. package/dist/config.js +1 -1
  73. package/dist/contradiction-review-WIUBAR52.js +21 -0
  74. package/dist/contradiction-review-WIUBAR52.js.map +1 -0
  75. package/dist/contradiction-scan-GR33PONM.js +376 -0
  76. package/dist/contradiction-scan-GR33PONM.js.map +1 -0
  77. package/dist/direct-answer-wiring.d.ts +77 -0
  78. package/dist/direct-answer-wiring.js +75 -0
  79. package/dist/direct-answer-wiring.js.map +1 -0
  80. package/dist/direct-answer.d.ts +106 -0
  81. package/dist/direct-answer.js +10 -0
  82. package/dist/direct-answer.js.map +1 -0
  83. package/dist/{engine-X7X3AAG3.js → engine-5TIQBYZR.js} +7 -7
  84. package/dist/engine-5TIQBYZR.js.map +1 -0
  85. package/dist/entity-retrieval.js +6 -6
  86. package/dist/explicit-capture.d.ts +1 -1
  87. package/dist/extraction.js +6 -6
  88. package/dist/fallback-llm.d.ts +11 -2
  89. package/dist/fallback-llm.js +3 -3
  90. package/dist/harmonic-retrieval.js +2 -1
  91. package/dist/index.d.ts +10 -124
  92. package/dist/index.js +74 -137
  93. package/dist/index.js.map +1 -1
  94. package/dist/intent.js +1 -1
  95. package/dist/local-llm.d.ts +10 -3
  96. package/dist/local-llm.js +1 -1
  97. package/dist/operator-toolkit.js +12 -11
  98. package/dist/{orchestrator-B9kwlCep.d.ts → orchestrator-DRYA6_lW.d.ts} +21 -2
  99. package/dist/orchestrator.d.ts +1 -1
  100. package/dist/orchestrator.js +36 -35
  101. package/dist/qmd.js +2 -1
  102. package/dist/recall-state.d.ts +28 -1
  103. package/dist/recall-state.js +1 -1
  104. package/dist/resolution-QBTDHTG7.js +100 -0
  105. package/dist/resolution-QBTDHTG7.js.map +1 -0
  106. package/dist/resolve-provider-secret.d.ts +24 -1
  107. package/dist/resolve-provider-secret.js +3 -1
  108. package/dist/resume-bundles.js +3 -3
  109. package/dist/schemas.d.ts +14 -14
  110. package/dist/semantic-consolidation.js +6 -6
  111. package/dist/semantic-rule-promotion.js +6 -6
  112. package/dist/semantic-rule-verifier.js +6 -6
  113. package/dist/storage.js +5 -5
  114. package/dist/summarizer.js +5 -5
  115. package/dist/types-DJhqDJUV.d.ts +50 -0
  116. package/dist/types.d.ts +34 -2
  117. package/dist/types.js +1 -1
  118. package/dist/verified-recall.js +6 -6
  119. package/package.json +1 -1
  120. package/dist/chunk-2VFW5K5U.js.map +0 -1
  121. package/dist/chunk-7PA4OZEU.js.map +0 -1
  122. package/dist/chunk-AAI7JARD.js.map +0 -1
  123. package/dist/chunk-BKQJBXXX.js.map +0 -1
  124. package/dist/chunk-HITJFT7E.js.map +0 -1
  125. package/dist/chunk-KEG4GNGI.js.map +0 -1
  126. package/dist/chunk-NSB3WSYS.js.map +0 -1
  127. package/dist/chunk-OJFGVJS6.js.map +0 -1
  128. package/dist/chunk-PMB3WGDL.js.map +0 -1
  129. package/dist/chunk-QKAH5B6E.js.map +0 -1
  130. package/dist/chunk-S4LX5EBI.js.map +0 -1
  131. package/dist/chunk-U2IQTSBY.js.map +0 -1
  132. package/dist/chunk-UPMD5XND.js.map +0 -1
  133. package/dist/chunk-V7XCAHIB.js.map +0 -1
  134. package/dist/chunk-XMGSSBFX.js.map +0 -1
  135. package/dist/chunk-YFYL2SIJ.js.map +0 -1
  136. /package/dist/{engine-X7X3AAG3.js.map → abort-error.js.map} +0 -0
  137. /package/dist/{chunk-X4WESCKA.js.map → chunk-3QHL5ABG.js.map} +0 -0
  138. /package/dist/{chunk-ECKDIK5F.js.map → chunk-4LACOVZX.js.map} +0 -0
  139. /package/dist/{chunk-AYPYCLR7.js.map → chunk-6LX5ORAS.js.map} +0 -0
  140. /package/dist/{chunk-GJQPH5G3.js.map → chunk-6UJ47TVX.js.map} +0 -0
  141. /package/dist/{chunk-KWP7T3DP.js.map → chunk-7ECD5ATE.js.map} +0 -0
  142. /package/dist/{chunk-74JR4N5J.js.map → chunk-7WQ6SLIE.js.map} +0 -0
  143. /package/dist/{chunk-JROGC36Y.js.map → chunk-DHHP2Z4X.js.map} +0 -0
  144. /package/dist/{chunk-POMSFKTB.js.map → chunk-GV6NLQ4X.js.map} +0 -0
  145. /package/dist/{chunk-POBPGDWI.js.map → chunk-ITRLGI2T.js.map} +0 -0
  146. /package/dist/{chunk-MYQWXITD.js.map → chunk-JIU55F3X.js.map} +0 -0
  147. /package/dist/{chunk-BTY5RRRF.js.map → chunk-N42IWANG.js.map} +0 -0
  148. /package/dist/{chunk-RCICHSHL.js.map → chunk-SYUK3VLY.js.map} +0 -0
@@ -53,13 +53,25 @@ async function getGatewayResolver() {
53
53
  async function findRuntimeModules() {
54
54
  const { readdirSync } = await import("fs");
55
55
  const { createRequire } = await import("module");
56
+ const { execFileSync } = await import("child_process");
56
57
  const candidates = [];
57
58
  const distDirs = [];
59
+ const pushDistDirs = (entryPath) => {
60
+ const resolvedEntryDir = path.dirname(entryPath);
61
+ const packageRoot = path.basename(resolvedEntryDir) === "dist" ? path.resolve(resolvedEntryDir, "..") : resolvedEntryDir;
62
+ const candidateDistDirs = [
63
+ path.join(packageRoot, "dist"),
64
+ path.join(packageRoot, "..", "dist")
65
+ ];
66
+ for (const candidate of candidateDistDirs) {
67
+ const resolved = path.resolve(candidate);
68
+ if (!distDirs.includes(resolved)) distDirs.push(resolved);
69
+ }
70
+ };
58
71
  try {
59
72
  const req = createRequire(import.meta.url);
60
73
  const openclawMain = req.resolve("openclaw");
61
- const openclawDist = path.join(path.dirname(openclawMain), "..", "dist");
62
- if (openclawDist) distDirs.push(path.resolve(openclawDist));
74
+ pushDistDirs(openclawMain);
63
75
  } catch {
64
76
  }
65
77
  try {
@@ -68,12 +80,22 @@ async function findRuntimeModules() {
68
80
  if (mainScript) {
69
81
  const realScript = realpathSync(mainScript);
70
82
  if (realScript.includes("openclaw")) {
71
- const distDir = path.dirname(realScript);
72
- if (!distDirs.includes(distDir)) distDirs.push(distDir);
83
+ pushDistDirs(realScript);
73
84
  }
74
85
  }
75
86
  } catch {
76
87
  }
88
+ try {
89
+ const openclawBin = execFileSync("which", ["openclaw"], {
90
+ encoding: "utf8",
91
+ stdio: ["ignore", "pipe", "ignore"]
92
+ }).trim();
93
+ if (openclawBin) {
94
+ const { realpathSync } = await import("fs");
95
+ pushDistDirs(realpathSync(openclawBin));
96
+ }
97
+ } catch {
98
+ }
77
99
  for (const dir of distDirs) {
78
100
  try {
79
101
  const files = readdirSync(dir);
@@ -88,7 +110,10 @@ async function findRuntimeModules() {
88
110
  return candidates;
89
111
  }
90
112
  async function resolveProviderApiKey(providerId, apiKeyValue, gatewayConfig, agentDir) {
91
- const cacheKey = `provider:${providerId}`;
113
+ const resolvedAgentDir = path.resolve(
114
+ agentDir ?? path.join(os.homedir(), ".openclaw", "agents", "main", "agent")
115
+ );
116
+ const cacheKey = `provider:${providerId}:agentDir:${resolvedAgentDir}`;
92
117
  if (resolvedCache.has(cacheKey)) {
93
118
  return resolvedCache.get(cacheKey);
94
119
  }
@@ -104,7 +129,6 @@ async function resolveProviderApiKey(providerId, apiKeyValue, gatewayConfig, age
104
129
  const resolver = await getGatewayResolver();
105
130
  if (resolver) {
106
131
  try {
107
- const resolvedAgentDir = agentDir ?? path.join(os.homedir(), ".openclaw", "agents", "main", "agent");
108
132
  const auth = await resolver({ provider: providerId, cfg: gatewayConfig, agentDir: resolvedAgentDir });
109
133
  if (auth?.apiKey) {
110
134
  resolved = auth.apiKey;
@@ -154,10 +178,16 @@ function clearSecretCache() {
154
178
  _resolverLoaded = false;
155
179
  _resolverNextRetryAt = 0;
156
180
  }
181
+ function __setGatewayResolverForTest(resolver) {
182
+ _resolveApiKeyForProvider = resolver;
183
+ _resolverLoaded = resolver !== null;
184
+ _resolverNextRetryAt = 0;
185
+ }
157
186
 
158
187
  export {
159
188
  resolveProviderApiKey,
160
189
  getGatewayRuntimeAuthForModel,
161
- clearSecretCache
190
+ clearSecretCache,
191
+ __setGatewayResolverForTest
162
192
  };
163
- //# sourceMappingURL=chunk-KEG4GNGI.js.map
193
+ //# sourceMappingURL=chunk-XZ2TIKGC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/resolve-provider-secret.ts"],"sourcesContent":["import { log } from \"./logger.js\";\nimport { readEnvVar } from \"./runtime/env.js\";\nimport path from \"node:path\";\nimport os from \"node:os\";\n\n/**\n * Resolve a provider API key using OpenClaw's own auth resolution system.\n *\n * This module delegates to the gateway's `resolveApiKeyForProvider()` function,\n * which handles all secret reference formats (SecretRef objects, auth profiles,\n * \"secretref-managed\" markers, environment variables, etc.) using the same\n * codepath the gateway uses for its own agent sessions.\n *\n * For plain-text API keys, a fast path returns them directly without\n * involving the gateway auth system.\n *\n * Results are cached per provider for the gateway process lifetime.\n */\n\ntype ResolveApiKeyFn = (params: {\n provider: string;\n cfg?: unknown;\n agentDir?: string;\n}) => Promise<{ apiKey?: string; source?: string; mode?: string } | null>;\n\n/**\n * Resolve request-ready auth for a model, including provider-owned transforms\n * (e.g., OAuth token exchange, base URL override for openai-codex).\n */\nexport type GetRuntimeAuthForModelFn = (params: {\n model: { provider: string; id: string; api?: string; baseUrl?: string };\n cfg?: unknown;\n workspaceDir?: string;\n}) => Promise<{\n apiKey?: string;\n baseUrl?: string;\n source?: string;\n mode?: string;\n profileId?: string;\n} | null>;\n\nlet _resolveApiKeyForProvider: ResolveApiKeyFn | null = null;\nlet _getRuntimeAuthForModel: GetRuntimeAuthForModelFn | null = null;\nlet _resolverLoaded = false;\nlet _resolverNextRetryAt = 0;\nconst RESOLVER_RETRY_BACKOFF_MS = 60_000; // 1 minute between retries after first failure\nconst resolvedCache = new Map<string, string | undefined>();\n\n/**\n * Lazily load the gateway's resolveApiKeyForProvider function.\n * Returns null if not available (e.g., running outside the gateway process).\n */\nasync function getGatewayResolver(): Promise<ResolveApiKeyFn | null> {\n if (_resolverLoaded) {\n return _resolveApiKeyForProvider;\n }\n // Backoff: don't re-scan filesystem on every call when module wasn't found.\n // After a failure, wait RESOLVER_RETRY_BACKOFF_MS before trying again.\n if (_resolverNextRetryAt > 0 && Date.now() < _resolverNextRetryAt) {\n return null;\n }\n\n try {\n // The gateway bundles this in a runtime chunk — import it dynamically.\n // This import path is stable across gateway versions since it's a named runtime export.\n const candidates = [\n // Try glob-matching the runtime module name (hash varies per build)\n ...await findRuntimeModules(),\n ];\n\n const { pathToFileURL } = await import(\"node:url\");\n for (const candidate of candidates) {\n try {\n // Convert native path to file:// URL for cross-platform ESM import compatibility\n const importUrl = pathToFileURL(candidate).href;\n const mod = await import(importUrl);\n if (typeof mod.resolveApiKeyForProvider === \"function\") {\n _resolveApiKeyForProvider = mod.resolveApiKeyForProvider;\n if (typeof mod.getRuntimeAuthForModel === \"function\") {\n _getRuntimeAuthForModel = mod.getRuntimeAuthForModel;\n log.debug(\"loaded gateway getRuntimeAuthForModel from runtime module\");\n }\n _resolverLoaded = true;\n log.debug(\"loaded gateway resolveApiKeyForProvider from runtime module\");\n return _resolveApiKeyForProvider;\n }\n } catch {\n // Try next candidate\n }\n }\n } catch {\n // Silent\n }\n\n // Backoff before retrying — avoid repeated fs scanning.\n // Retries after RESOLVER_RETRY_BACKOFF_MS so the resolver can\n // recover if the gateway restarts or the module becomes available.\n _resolverNextRetryAt = Date.now() + RESOLVER_RETRY_BACKOFF_MS;\n log.debug(`gateway resolveApiKeyForProvider not available — will retry after ${RESOLVER_RETRY_BACKOFF_MS / 1000}s`);\n return null;\n}\n\n/**\n * Find the gateway's model-auth runtime module by scanning the dist directory.\n * Uses require.resolve to find the openclaw package regardless of install method.\n */\nasync function findRuntimeModules(): Promise<string[]> {\n const { readdirSync } = await import(\"node:fs\");\n const { createRequire } = await import(\"node:module\");\n const { execFileSync } = await import(\"node:child_process\");\n const candidates: string[] = [];\n\n // Discover the openclaw dist directory from the installed package,\n // regardless of how it was installed (Homebrew, npm global, local, etc.)\n const distDirs: string[] = [];\n const pushDistDirs = (entryPath: string): void => {\n const resolvedEntryDir = path.dirname(entryPath);\n const packageRoot = path.basename(resolvedEntryDir) === \"dist\"\n ? path.resolve(resolvedEntryDir, \"..\")\n : resolvedEntryDir;\n const candidateDistDirs = [\n path.join(packageRoot, \"dist\"),\n path.join(packageRoot, \"..\", \"dist\"),\n ];\n for (const candidate of candidateDistDirs) {\n const resolved = path.resolve(candidate);\n if (!distDirs.includes(resolved)) distDirs.push(resolved);\n }\n };\n\n try {\n // require.resolve finds the package from the current process context\n const req = createRequire(import.meta.url);\n const openclawMain = req.resolve(\"openclaw\");\n pushDistDirs(openclawMain);\n } catch {\n // openclaw not resolvable from plugin context — try alternate paths\n }\n\n // Fallback: infer from the running process (gateway runs from its own dist/)\n // Use fs.realpathSync to resolve symlinks (e.g., /usr/local/bin/openclaw → actual path)\n try {\n const { realpathSync } = await import(\"node:fs\");\n const mainScript = process.argv[1];\n if (mainScript) {\n const realScript = realpathSync(mainScript);\n if (realScript.includes(\"openclaw\")) {\n pushDistDirs(realScript);\n }\n }\n } catch {\n // Silent\n }\n\n // Fallback: inspect the installed openclaw binary on PATH (Homebrew/global npm).\n try {\n const openclawBin = execFileSync(\"which\", [\"openclaw\"], {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n }).trim();\n if (openclawBin) {\n const { realpathSync } = await import(\"node:fs\");\n pushDistDirs(realpathSync(openclawBin));\n }\n } catch {\n // Silent\n }\n\n for (const dir of distDirs) {\n try {\n const files = readdirSync(dir);\n for (const f of files) {\n if (f.startsWith(\"runtime-model-auth.runtime-\") && f.endsWith(\".js\")) {\n candidates.push(path.join(dir, f));\n }\n }\n } catch {\n // Directory doesn't exist — skip\n }\n }\n\n return candidates;\n}\n\n/**\n * Resolve a provider API key from various OpenClaw formats.\n *\n * Resolution order:\n * 1. Plain-text string → returned immediately\n * 2. Gateway's resolveApiKeyForProvider → handles all secret ref formats\n * 3. Environment variable fallback (PROVIDER_NAME_API_KEY)\n * 4. undefined → provider is skipped in the fallback chain\n */\nexport async function resolveProviderApiKey(\n providerId: string,\n apiKeyValue: unknown,\n gatewayConfig?: unknown,\n agentDir?: string,\n): Promise<string | undefined> {\n const resolvedAgentDir = path.resolve(\n agentDir ?? path.join(os.homedir(), \".openclaw\", \"agents\", \"main\", \"agent\"),\n );\n\n // Check cache first\n const cacheKey = `provider:${providerId}:agentDir:${resolvedAgentDir}`;\n if (resolvedCache.has(cacheKey)) {\n return resolvedCache.get(cacheKey);\n }\n\n let resolved: string | undefined;\n\n // Fast path: plain-text string that looks like an actual API key\n if (typeof apiKeyValue === \"string\" && apiKeyValue.trim().length > 0) {\n // Skip known non-API-key markers used by the gateway for auth modes\n // that don't use bearer tokens (OAuth, local endpoints, GCP credentials)\n if (\n apiKeyValue === \"secretref-managed\" ||\n apiKeyValue.endsWith(\"-oauth\") ||\n apiKeyValue.endsWith(\"-local\") ||\n apiKeyValue === \"lm-studio\" ||\n apiKeyValue.startsWith(\"gcp-\")\n ) {\n // Fall through to gateway resolver / env var fallback\n } else {\n resolved = apiKeyValue;\n resolvedCache.set(cacheKey, resolved);\n return resolved;\n }\n }\n\n // The API key is either a SecretRef object, \"secretref-managed\", or empty.\n // Try the gateway's own auth resolution system first.\n const resolver = await getGatewayResolver();\n if (resolver) {\n try {\n const auth = await resolver({ provider: providerId, cfg: gatewayConfig, agentDir: resolvedAgentDir });\n if (auth?.apiKey) {\n resolved = auth.apiKey;\n log.debug(`resolved API key for provider \"${providerId}\" via gateway auth (source: ${auth.source ?? \"unknown\"}, mode: ${auth.mode ?? \"unknown\"})`);\n resolvedCache.set(cacheKey, resolved);\n return resolved;\n }\n } catch (err) {\n log.debug(\n `gateway auth resolution failed for provider \"${providerId}\": ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n\n // Environment variable fallback\n resolved = resolveFromEnv(providerId);\n if (resolved) {\n log.debug(`resolved API key for provider \"${providerId}\" from environment variable`);\n } else {\n log.debug(`could not resolve API key for provider \"${providerId}\" — skipping`);\n }\n\n // Only cache successful resolutions — failures are retried on next call\n // so providers can recover after transient issues (e.g., 1Password agent restart)\n if (resolved) {\n resolvedCache.set(cacheKey, resolved);\n }\n return resolved;\n}\n\n/**\n * Try to resolve an API key from environment variables.\n */\nfunction resolveFromEnv(providerId: string): string | undefined {\n const normalized = providerId.toUpperCase().replace(/[^A-Z0-9]/g, \"_\");\n const candidates = [\n `${normalized}_API_KEY`,\n `${normalized}_TOKEN`,\n ];\n for (const envVar of candidates) {\n const value = readEnvVar(envVar);\n if (value && value.trim().length > 0) {\n return value.trim();\n }\n }\n return undefined;\n}\n\n/**\n * Get the gateway's getRuntimeAuthForModel function, if available.\n * This resolves request-ready auth including provider-owned transforms\n * (OAuth token exchange, base URL override for codex/copilot/etc.).\n * Must be called after at least one resolveProviderApiKey() call to\n * trigger the lazy module load.\n */\nexport async function getGatewayRuntimeAuthForModel(): Promise<GetRuntimeAuthForModelFn | null> {\n // Ensure the runtime module has been loaded\n await getGatewayResolver();\n return _getRuntimeAuthForModel;\n}\n\n/**\n * Clear the resolution cache (useful for testing or key rotation).\n */\nexport function clearSecretCache(): void {\n resolvedCache.clear();\n _resolveApiKeyForProvider = null;\n _getRuntimeAuthForModel = null;\n _resolverLoaded = false;\n _resolverNextRetryAt = 0;\n}\n\nexport function __setGatewayResolverForTest(resolver: ResolveApiKeyFn | null): void {\n _resolveApiKeyForProvider = resolver;\n _resolverLoaded = resolver !== null;\n _resolverNextRetryAt = 0;\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,140 @@
1
+ import {
2
+ countRecallTokenOverlap,
3
+ normalizeRecallTokens
4
+ } from "./chunk-DT5TVLJE.js";
5
+
6
+ // src/direct-answer.ts
7
+ var FILTER_LABELS = {
8
+ nonActiveStatus: "non-active-status",
9
+ notTrustedZone: "not-trusted-zone",
10
+ ineligibleTaxonomyBucket: "ineligible-taxonomy-bucket",
11
+ belowImportanceFloor: "below-importance-floor",
12
+ entityRefMismatch: "entity-ref-mismatch",
13
+ belowTokenOverlapFloor: "below-token-overlap-floor"
14
+ };
15
+ function isDirectAnswerEligible(input) {
16
+ const { query, candidates, config, queryEntityRefs } = input;
17
+ if (!config.enabled) {
18
+ return {
19
+ eligible: false,
20
+ reason: "disabled",
21
+ narrative: "direct-answer tier is disabled",
22
+ filteredBy: []
23
+ };
24
+ }
25
+ const queryTokens = new Set(normalizeRecallTokens(query));
26
+ if (queryTokens.size === 0) {
27
+ return {
28
+ eligible: false,
29
+ reason: "empty-query",
30
+ narrative: "query has no searchable tokens after normalization",
31
+ filteredBy: []
32
+ };
33
+ }
34
+ if (candidates.length === 0) {
35
+ return {
36
+ eligible: false,
37
+ reason: "no-candidates",
38
+ narrative: "no candidates supplied",
39
+ filteredBy: []
40
+ };
41
+ }
42
+ const filteredBy = [];
43
+ let working = candidates;
44
+ working = applyFilter(working, filteredBy, FILTER_LABELS.nonActiveStatus, (c) => {
45
+ const status = c.memory.frontmatter.status ?? "active";
46
+ return status === "active";
47
+ });
48
+ working = applyFilter(
49
+ working,
50
+ filteredBy,
51
+ FILTER_LABELS.notTrustedZone,
52
+ (c) => c.trustZone === "trusted"
53
+ );
54
+ working = applyFilter(
55
+ working,
56
+ filteredBy,
57
+ FILTER_LABELS.ineligibleTaxonomyBucket,
58
+ (c) => c.taxonomyBucket !== null && config.eligibleTaxonomyBuckets.includes(c.taxonomyBucket)
59
+ );
60
+ working = applyFilter(working, filteredBy, FILTER_LABELS.belowImportanceFloor, (c) => {
61
+ if (c.memory.frontmatter.verificationState === "user_confirmed") return true;
62
+ return c.importanceScore >= config.importanceFloor;
63
+ });
64
+ if (queryEntityRefs && queryEntityRefs.length > 0) {
65
+ const normRefs = new Set(queryEntityRefs.map((r) => r.toLowerCase()));
66
+ working = applyFilter(working, filteredBy, FILTER_LABELS.entityRefMismatch, (c) => {
67
+ const ref = c.memory.frontmatter.entityRef;
68
+ if (!ref) return true;
69
+ return normRefs.has(ref.toLowerCase());
70
+ });
71
+ }
72
+ if (working.length === 0) {
73
+ return {
74
+ eligible: false,
75
+ reason: "no-eligible-candidates",
76
+ narrative: "no candidates survived eligibility filters",
77
+ filteredBy
78
+ };
79
+ }
80
+ const scored = working.map((candidate) => {
81
+ const searchable = `${candidate.memory.frontmatter.tags?.join(" ") ?? ""} ${candidate.memory.content}`.trim();
82
+ const matches = countRecallTokenOverlap(queryTokens, searchable);
83
+ return { candidate, tokenOverlap: matches / queryTokens.size };
84
+ });
85
+ const overlapSurvivors = scored.filter((s) => s.tokenOverlap >= config.tokenOverlapFloor);
86
+ if (overlapSurvivors.length < scored.length) {
87
+ filteredBy.push(FILTER_LABELS.belowTokenOverlapFloor);
88
+ }
89
+ if (overlapSurvivors.length === 0) {
90
+ return {
91
+ eligible: false,
92
+ reason: "below-token-overlap-floor",
93
+ narrative: `no candidate met token-overlap floor ${config.tokenOverlapFloor}`,
94
+ filteredBy
95
+ };
96
+ }
97
+ overlapSurvivors.sort(compareScored);
98
+ if (overlapSurvivors.length >= 2) {
99
+ const topScore = scoreFor(overlapSurvivors[0]);
100
+ const secondScore = scoreFor(overlapSurvivors[1]);
101
+ if (topScore - secondScore < config.ambiguityMargin) {
102
+ return {
103
+ eligible: false,
104
+ reason: "ambiguous",
105
+ narrative: `top two candidates within ambiguityMargin ${config.ambiguityMargin}`,
106
+ filteredBy
107
+ };
108
+ }
109
+ }
110
+ const winner = overlapSurvivors[0];
111
+ const bucket = winner.candidate.taxonomyBucket ?? "unknown";
112
+ return {
113
+ eligible: true,
114
+ reason: "eligible",
115
+ winner: winner.candidate,
116
+ tokenOverlap: winner.tokenOverlap,
117
+ narrative: `trusted ${bucket}, unambiguous, token-overlap ${winner.tokenOverlap.toFixed(2)}`,
118
+ filteredBy
119
+ };
120
+ }
121
+ function applyFilter(working, filteredBy, label, keep) {
122
+ const before = working.length;
123
+ const next = working.filter(keep);
124
+ if (next.length < before) filteredBy.push(label);
125
+ return next;
126
+ }
127
+ function scoreFor(s) {
128
+ return s.candidate.matchScore ?? s.tokenOverlap;
129
+ }
130
+ function compareScored(a, b) {
131
+ const diff = scoreFor(b) - scoreFor(a);
132
+ if (diff !== 0) return diff;
133
+ return a.candidate.memory.path.localeCompare(b.candidate.memory.path);
134
+ }
135
+
136
+ export {
137
+ FILTER_LABELS,
138
+ isDirectAnswerEligible
139
+ };
140
+ //# sourceMappingURL=chunk-Y4FHOFJ2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/direct-answer.ts"],"sourcesContent":["/**\n * Direct-answer retrieval tier eligibility (issue #518 slice 2).\n *\n * This module is a pure decision layer. It takes a query, a set of\n * caller-resolved candidates (each already decorated with trust-zone,\n * taxonomy-bucket, and importance information), and the direct-answer\n * config, then returns an eligibility verdict.\n *\n * Keeping the module pure means:\n *\n * - Tests do not need a trust-zones store, taxonomy resolver, or importance\n * scorer on disk.\n * - Slice 3 (retrieval.ts wiring) is responsible for resolving those signals\n * before calling in; the wiring layer decides where candidates come from\n * (entity index, token prefilter, etc.). This module only decides\n * whether the surfaced candidates add up to a confident direct answer.\n *\n * Not wired into retrieval yet — see slice 3.\n */\n\nimport type { MemoryFile, MemoryStatus } from \"./types.js\";\nimport type { TrustZoneName } from \"./trust-zones.js\";\nimport {\n countRecallTokenOverlap,\n normalizeRecallTokens,\n} from \"./recall-tokenization.js\";\n\n/**\n * Caller-supplied candidate.\n *\n * `trustZone`, `taxonomyBucket`, and `importanceScore` are resolved outside\n * this module because each comes from a different subsystem. Passing them\n * as inputs keeps the function deterministic and easy to unit-test.\n *\n * `matchScore` is optional; if omitted, candidates are ranked by\n * token-overlap ratio. Callers that already computed a better ranking\n * score (e.g. BM25, vector similarity) can supply it to drive the\n * ambiguity check.\n */\nexport interface DirectAnswerCandidate {\n memory: MemoryFile;\n trustZone: TrustZoneName | null;\n taxonomyBucket: string | null;\n importanceScore: number;\n matchScore?: number;\n}\n\nexport interface DirectAnswerConfig {\n enabled: boolean;\n tokenOverlapFloor: number;\n importanceFloor: number;\n ambiguityMargin: number;\n eligibleTaxonomyBuckets: string[];\n}\n\nexport interface DirectAnswerInput {\n query: string;\n candidates: DirectAnswerCandidate[];\n config: DirectAnswerConfig;\n /**\n * Optional entity-ref hints resolved from the query upstream. When\n * supplied, a candidate with a set `entityRef` must match one of these\n * (case-insensitive) to remain eligible. Candidates without an\n * `entityRef` are allowed through regardless.\n */\n queryEntityRefs?: string[];\n}\n\nexport type DirectAnswerReason =\n | \"disabled\"\n | \"empty-query\"\n | \"no-candidates\"\n | \"no-eligible-candidates\"\n | \"below-token-overlap-floor\"\n | \"ambiguous\"\n | \"eligible\";\n\nexport interface DirectAnswerResult {\n eligible: boolean;\n reason: DirectAnswerReason;\n /** Winning candidate when eligible. */\n winner?: DirectAnswerCandidate;\n /** Computed token-overlap ratio (0..1) of the winner. */\n tokenOverlap?: number;\n /**\n * Human-readable summary suitable for\n * `RecallTierExplain.tierReason`.\n */\n narrative: string;\n /**\n * Filter labels that eliminated at least one candidate along the way.\n * Populated regardless of eligibility so the caller can surface the\n * narrowing steps in `RecallTierExplain.filteredBy`.\n */\n filteredBy: string[];\n}\n\n/** Filter labels — exported so callers and tests can match them structurally. */\nexport const FILTER_LABELS = {\n nonActiveStatus: \"non-active-status\",\n notTrustedZone: \"not-trusted-zone\",\n ineligibleTaxonomyBucket: \"ineligible-taxonomy-bucket\",\n belowImportanceFloor: \"below-importance-floor\",\n entityRefMismatch: \"entity-ref-mismatch\",\n belowTokenOverlapFloor: \"below-token-overlap-floor\",\n} as const;\n\ninterface ScoredCandidate {\n candidate: DirectAnswerCandidate;\n tokenOverlap: number;\n}\n\n/**\n * Determine whether a query can be served by the direct-answer tier.\n *\n * Decision ladder, in order:\n *\n * 1. config.enabled === false → \"disabled\"\n * 2. empty query tokens → \"empty-query\"\n * 3. empty candidates → \"no-candidates\"\n * 4. hard filters drop all candidates → \"no-eligible-candidates\"\n * 5. token-overlap floor drops all → \"below-token-overlap-floor\"\n * 6. top two candidates within ambiguityMargin → \"ambiguous\"\n * 7. otherwise → \"eligible\"\n */\nexport function isDirectAnswerEligible(\n input: DirectAnswerInput,\n): DirectAnswerResult {\n const { query, candidates, config, queryEntityRefs } = input;\n\n if (!config.enabled) {\n return {\n eligible: false,\n reason: \"disabled\",\n narrative: \"direct-answer tier is disabled\",\n filteredBy: [],\n };\n }\n\n const queryTokens = new Set(normalizeRecallTokens(query));\n if (queryTokens.size === 0) {\n return {\n eligible: false,\n reason: \"empty-query\",\n narrative: \"query has no searchable tokens after normalization\",\n filteredBy: [],\n };\n }\n\n if (candidates.length === 0) {\n return {\n eligible: false,\n reason: \"no-candidates\",\n narrative: \"no candidates supplied\",\n filteredBy: [],\n };\n }\n\n const filteredBy: string[] = [];\n let working: DirectAnswerCandidate[] = candidates;\n\n working = applyFilter(working, filteredBy, FILTER_LABELS.nonActiveStatus, (c) => {\n const status: MemoryStatus = c.memory.frontmatter.status ?? \"active\";\n return status === \"active\";\n });\n\n working = applyFilter(working, filteredBy, FILTER_LABELS.notTrustedZone, (c) =>\n c.trustZone === \"trusted\",\n );\n\n working = applyFilter(\n working,\n filteredBy,\n FILTER_LABELS.ineligibleTaxonomyBucket,\n (c) =>\n c.taxonomyBucket !== null &&\n config.eligibleTaxonomyBuckets.includes(c.taxonomyBucket),\n );\n\n working = applyFilter(working, filteredBy, FILTER_LABELS.belowImportanceFloor, (c) => {\n if (c.memory.frontmatter.verificationState === \"user_confirmed\") return true;\n return c.importanceScore >= config.importanceFloor;\n });\n\n if (queryEntityRefs && queryEntityRefs.length > 0) {\n const normRefs = new Set(queryEntityRefs.map((r) => r.toLowerCase()));\n working = applyFilter(working, filteredBy, FILTER_LABELS.entityRefMismatch, (c) => {\n const ref = c.memory.frontmatter.entityRef;\n if (!ref) return true;\n return normRefs.has(ref.toLowerCase());\n });\n }\n\n if (working.length === 0) {\n return {\n eligible: false,\n reason: \"no-eligible-candidates\",\n narrative: \"no candidates survived eligibility filters\",\n filteredBy,\n };\n }\n\n const scored: ScoredCandidate[] = working.map((candidate) => {\n const searchable =\n `${candidate.memory.frontmatter.tags?.join(\" \") ?? \"\"} ${candidate.memory.content}`.trim();\n const matches = countRecallTokenOverlap(queryTokens, searchable);\n return { candidate, tokenOverlap: matches / queryTokens.size };\n });\n\n const overlapSurvivors = scored.filter((s) => s.tokenOverlap >= config.tokenOverlapFloor);\n if (overlapSurvivors.length < scored.length) {\n filteredBy.push(FILTER_LABELS.belowTokenOverlapFloor);\n }\n\n if (overlapSurvivors.length === 0) {\n return {\n eligible: false,\n reason: \"below-token-overlap-floor\",\n narrative: `no candidate met token-overlap floor ${config.tokenOverlapFloor}`,\n filteredBy,\n };\n }\n\n overlapSurvivors.sort(compareScored);\n\n if (overlapSurvivors.length >= 2) {\n const topScore = scoreFor(overlapSurvivors[0]);\n const secondScore = scoreFor(overlapSurvivors[1]);\n if (topScore - secondScore < config.ambiguityMargin) {\n return {\n eligible: false,\n reason: \"ambiguous\",\n narrative: `top two candidates within ambiguityMargin ${config.ambiguityMargin}`,\n filteredBy,\n };\n }\n }\n\n const winner = overlapSurvivors[0];\n const bucket = winner.candidate.taxonomyBucket ?? \"unknown\";\n return {\n eligible: true,\n reason: \"eligible\",\n winner: winner.candidate,\n tokenOverlap: winner.tokenOverlap,\n narrative: `trusted ${bucket}, unambiguous, token-overlap ${winner.tokenOverlap.toFixed(2)}`,\n filteredBy,\n };\n}\n\nfunction applyFilter(\n working: DirectAnswerCandidate[],\n filteredBy: string[],\n label: string,\n keep: (c: DirectAnswerCandidate) => boolean,\n): DirectAnswerCandidate[] {\n const before = working.length;\n const next = working.filter(keep);\n if (next.length < before) filteredBy.push(label);\n return next;\n}\n\nfunction scoreFor(s: ScoredCandidate): number {\n return s.candidate.matchScore ?? s.tokenOverlap;\n}\n\nfunction compareScored(a: ScoredCandidate, b: ScoredCandidate): number {\n const diff = scoreFor(b) - scoreFor(a);\n if (diff !== 0) return diff;\n // Stable secondary key on path so the comparator returns 0 only for equal\n // entries (CLAUDE.md rule 19).\n return a.candidate.memory.path.localeCompare(b.candidate.memory.path);\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,137 @@
1
+ // 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
+ };
137
+ //# sourceMappingURL=chunk-YNB73F22.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/contradiction/contradiction-review.ts"],"sourcesContent":["/**\n * Contradiction Review Queue — storage for detected contradiction pairs (issue #520).\n *\n * Stores candidate pairs as JSON files under `memoryDir/.review/contradictions/`.\n * Pair IDs are deterministic (sha256 of sorted memory IDs) so reruns are idempotent.\n *\n * Lifecycle:\n * - `contradicts` → awaiting user review\n * - `duplicates` → auto-flagged for dedup (still needs user approval)\n * - `independent` / `both-valid` → dormant with cooldown\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { createHash } from \"node:crypto\";\nimport { log } from \"../logger.js\";\nimport type { ContradictionVerdict } from \"./contradiction-judge.js\";\n\n// ── Types ──────────────────────────────────────────────────────────────────────\n\nexport type ResolutionVerb = \"keep-a\" | \"keep-b\" | \"merge\" | \"both-valid\" | \"needs-more-context\";\n\nexport interface ContradictionPair {\n /** Deterministic pair ID: sha256(sorted(memoryIdA, memoryIdB)). */\n pairId: string;\n /** Memory IDs (sorted). */\n memoryIds: [string, string];\n /** Judge verdict. */\n verdict: ContradictionVerdict;\n /** Judge rationale. */\n rationale: string;\n /** Judge confidence in [0, 1]. */\n confidence: number;\n /** ISO timestamp when detected. */\n detectedAt: string;\n /** ISO timestamp when last reviewed by user. */\n lastReviewedAt?: string;\n /** Resolution verb applied by user. */\n resolution?: ResolutionVerb;\n /** Namespace scope. */\n namespace?: string;\n}\n\nexport interface ContradictionListResult {\n pairs: ContradictionPair[];\n total: number;\n durationMs: number;\n}\n\nexport type ContradictionFilter = ContradictionVerdict | \"all\" | \"unresolved\";\n\n// ── Helpers ────────────────────────────────────────────────────────────────────\n\nexport function computePairId(memoryIdA: string, memoryIdB: string): string {\n const sorted = [memoryIdA, memoryIdB].sort();\n return createHash(\"sha256\").update(sorted.join(\"::\")).digest(\"hex\").slice(0, 24);\n}\n\nfunction reviewDir(memoryDir: string): string {\n return path.join(memoryDir, \".review\", \"contradictions\");\n}\n\nfunction pairPath(memoryDir: string, pairId: string): string {\n if (pairId.includes(\"/\") || pairId.includes(\"\\\\\") || pairId.includes(\"..\")) {\n throw new Error(`Invalid pairId: ${pairId}`);\n }\n return path.join(reviewDir(memoryDir), `${pairId}.json`);\n}\n\nfunction ensureDir(memoryDir: string): void {\n const dir = reviewDir(memoryDir);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n}\n\n// ── Write ──────────────────────────────────────────────────────────────────────\n\n/**\n * Write a contradiction pair to the review queue.\n * Idempotent: if the pair already exists with a higher or equal confidence,\n * the existing entry is preserved.\n */\nexport function writePair(memoryDir: string, pair: Omit<ContradictionPair, \"pairId\"> & { memoryIds: [string, string] }): ContradictionPair {\n ensureDir(memoryDir);\n const pairId = computePairId(pair.memoryIds[0], pair.memoryIds[1]);\n const existing = readPair(memoryDir, pairId);\n\n // Preserve user resolution if already reviewed\n if (existing?.resolution) {\n return existing;\n }\n\n // Preserve cooldown: don't overwrite a cooled-down entry with lower confidence\n if (existing && existing.confidence >= pair.confidence) {\n return existing;\n }\n\n const full: ContradictionPair = {\n ...pair,\n pairId,\n lastReviewedAt: existing?.lastReviewedAt,\n resolution: existing?.resolution,\n };\n\n const filePath = pairPath(memoryDir, pairId);\n const tmpPath = `${filePath}.tmp`;\n\n // Atomic write: temp then rename (rule 54)\n fs.writeFileSync(tmpPath, JSON.stringify(full, null, 2), \"utf-8\");\n fs.renameSync(tmpPath, filePath);\n\n return full;\n}\n\n/**\n * Write multiple pairs, deduplicating inputs first (rule 49).\n */\nexport function writePairs(memoryDir: string, pairs: Array<Omit<ContradictionPair, \"pairId\"> & { memoryIds: [string, string] }>): ContradictionPair[] {\n const seen = new Set<string>();\n const results: ContradictionPair[] = [];\n\n for (const pair of pairs) {\n const key = computePairId(pair.memoryIds[0], pair.memoryIds[1]);\n if (seen.has(key)) continue;\n seen.add(key);\n results.push(writePair(memoryDir, pair));\n }\n\n return results;\n}\n\n// ── Read ───────────────────────────────────────────────────────────────────────\n\n/**\n * Read a single pair by ID. Returns null if not found.\n */\nexport function readPair(memoryDir: string, pairId: string): ContradictionPair | null {\n const filePath = pairPath(memoryDir, pairId);\n try {\n const raw = fs.readFileSync(filePath, \"utf-8\");\n const parsed = JSON.parse(raw);\n if (typeof parsed === \"object\" && parsed !== null && Array.isArray(parsed.memoryIds)) {\n return parsed as ContradictionPair;\n }\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * List pairs in the review queue, optionally filtered by verdict.\n */\nexport function listPairs(\n memoryDir: string,\n options?: {\n filter?: ContradictionFilter;\n namespace?: string;\n limit?: number;\n },\n): ContradictionListResult {\n const startTime = Date.now();\n const dir = reviewDir(memoryDir);\n const { filter = \"all\", namespace, limit = 50 } = options ?? {};\n const pairs: ContradictionPair[] = [];\n let total = 0;\n\n if (!fs.existsSync(dir)) {\n return { pairs: [], total: 0, durationMs: Date.now() - startTime };\n }\n\n for (const entry of fs.readdirSync(dir)) {\n if (!entry.endsWith(\".json\")) continue;\n\n try {\n const raw = fs.readFileSync(path.join(dir, entry), \"utf-8\");\n const pair = JSON.parse(raw) as ContradictionPair;\n\n if (typeof pair !== \"object\" || pair === null) continue;\n if (!Array.isArray(pair.memoryIds)) continue;\n\n // Namespace filter\n if (namespace && pair.namespace !== namespace) continue;\n\n // Verdict filter\n if (filter === \"unresolved\") {\n if (pair.resolution) continue;\n if (pair.verdict === \"independent\") continue;\n } else if (filter !== \"all\" && pair.verdict !== filter) {\n continue;\n }\n\n total++;\n if (pairs.length < limit) pairs.push(pair);\n } catch {\n continue;\n }\n }\n\n return { pairs, total, durationMs: Date.now() - startTime };\n}\n\n// ── Cooldown ───────────────────────────────────────────────────────────────────\n\n/**\n * Check if a pair is within its cooldown window.\n * Returns true if the pair should be SKIPPED (still cooling down).\n */\nexport function isCoolingDown(pair: ContradictionPair, cooldownDays: number): boolean {\n if (cooldownDays <= 0) return false; // rule 27: guard against 0\n if (!pair.lastReviewedAt) return false;\n\n const lastReviewed = new Date(pair.lastReviewedAt).getTime();\n if (!Number.isFinite(lastReviewed)) return false;\n\n const cooldownMs = cooldownDays * 24 * 60 * 60 * 1000;\n return Date.now() < lastReviewed + cooldownMs;\n}\n\n/**\n * Mark a pair as reviewed (sets lastReviewedAt and resolution).\n */\nexport function resolvePair(\n memoryDir: string,\n pairId: string,\n verb: ResolutionVerb,\n): ContradictionPair | null {\n const existing = readPair(memoryDir, pairId);\n if (!existing) return null;\n\n const updated: ContradictionPair = {\n ...existing,\n lastReviewedAt: new Date().toISOString(),\n resolution: verb,\n };\n\n const filePath = pairPath(memoryDir, pairId);\n const tmpPath = `${filePath}.tmp`;\n\n fs.writeFileSync(tmpPath, JSON.stringify(updated, null, 2), \"utf-8\");\n fs.renameSync(tmpPath, filePath);\n\n return updated;\n}\n\n/**\n * Check whether a pair's referenced memories have changed since detection,\n * which should override cooldown.\n */\nexport function memoryHashesChanged(\n _memoryDir: string,\n _pair: ContradictionPair,\n _getCurrentHash: (memoryId: string) => string | null,\n): boolean {\n // Intentionally a stub for now — the full implementation would compare\n // content hashes stored at detection time with current hashes.\n return false;\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  QmdClient
3
- } from "./chunk-7PA4OZEU.js";
3
+ } from "./chunk-BLKTA7MM.js";
4
4
  import {
5
5
  launchProcess
6
6
  } from "./chunk-LK6SGL53.js";
@@ -9,7 +9,7 @@ import {
9
9
  } from "./chunk-QNJMBKFK.js";
10
10
  import {
11
11
  StorageManager
12
- } from "./chunk-POMSFKTB.js";
12
+ } from "./chunk-GV6NLQ4X.js";
13
13
  import {
14
14
  log
15
15
  } from "./chunk-2ODBA7MQ.js";
@@ -104,8 +104,13 @@ async function scanDir(dir) {
104
104
  async function scanMemoryDir(memoryDir) {
105
105
  const factsDir = path.join(memoryDir, "facts");
106
106
  const correctionsDir = path.join(memoryDir, "corrections");
107
- const [facts, corrections] = await Promise.all([scanDir(factsDir), scanDir(correctionsDir)]);
108
- return [...facts, ...corrections];
107
+ const proceduresDir = path.join(memoryDir, "procedures");
108
+ const [facts, corrections, procedures] = await Promise.all([
109
+ scanDir(factsDir),
110
+ scanDir(correctionsDir),
111
+ scanDir(proceduresDir)
112
+ ]);
113
+ return [...facts, ...corrections, ...procedures];
109
114
  }
110
115
 
111
116
  // src/search/lancedb-backend.ts
@@ -1897,4 +1902,4 @@ export {
1897
1902
  namespaceCollectionName,
1898
1903
  NamespaceSearchRouter
1899
1904
  };
1900
- //# sourceMappingURL=chunk-HITJFT7E.js.map
1905
+ //# sourceMappingURL=chunk-ZVBB3T7V.js.map