@remnic/plugin-openclaw 1.0.34 → 1.0.35

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
@@ -1,8 +1,8 @@
1
1
  # @remnic/plugin-openclaw
2
2
 
3
- OpenClaw plugin for Remnic memory. The package bundles the OpenClaw adapter plus the Remnic core runtime so it can run without a separate Remnic service; the adapter registers OpenClaw hooks/tools and delegates memory behavior to [`@remnic/core`](https://www.npmjs.com/package/@remnic/core).
3
+ OpenClaw plugin for Remnic memory and context. The package bundles the OpenClaw adapter plus the Remnic core runtime so it can run without a separate Remnic service; the adapter registers OpenClaw hooks/tools and delegates memory behavior to [`@remnic/core`](https://www.npmjs.com/package/@remnic/core).
4
4
 
5
- Part of [Remnic](https://github.com/joshuaswarren/remnic), the universal memory layer for AI agents.
5
+ Part of [Remnic](https://github.com/joshuaswarren/remnic), open-source memory and context for user-aware agents.
6
6
 
7
7
  ## Install
8
8
 
@@ -12,7 +12,7 @@ openclaw plugins install clawhub:@remnic/plugin-openclaw
12
12
 
13
13
  Or ask your OpenClaw agent:
14
14
 
15
- > Install the @remnic/plugin-openclaw plugin and configure it as my memory system.
15
+ > Install the @remnic/plugin-openclaw ClawHub plugin and configure it as my memory system.
16
16
 
17
17
  ## Configure
18
18
 
@@ -86,7 +86,7 @@ This plugin hooks into the OpenClaw gateway lifecycle:
86
86
  - **`agent_end`** -- buffers the conversation turn for extraction
87
87
  - **`before_compaction`** / **`after_compaction`** -- saves checkpoints and triggers session reset on context compaction
88
88
  - **`before_reset`** -- bounded flush of the in-flight buffer before OpenClaw discards a session
89
- - **`commands.list`** -- exposes Remnic slash-command descriptors to the command palette
89
+ - **`api.registerCommand()`** -- exposes Remnic slash-command descriptors to the command palette
90
90
  - **`session_start`** / **`session_end`** -- session lifecycle tracking
91
91
  - **`before_tool_call`** / **`after_tool_call`** -- tool usage observation for analytics
92
92
  - **`llm_output`** -- LLM token usage tracking
@@ -164,13 +164,28 @@ CI jobs that provision OpenClaw should use
164
164
  `npm run check:openclaw-sdk-surface:required` or pass
165
165
  `-- --require --package-root <path>` so a missing SDK fails instead of skipping.
166
166
 
167
- Last compatibility sweep: May 7, 2026. The SDK surface check passed against
167
+ Last compatibility sweep: May 16, 2026. The SDK surface check passed against
168
168
  `openclaw@2026.5.3`, `openclaw@2026.5.3-1`, `openclaw@2026.5.4-beta.1`,
169
169
  `openclaw@2026.5.4-beta.2`, `openclaw@2026.5.4-beta.3`,
170
- `openclaw@2026.5.4`, `openclaw@2026.5.5`, and `openclaw@2026.5.6`.
170
+ `openclaw@2026.5.4`, `openclaw@2026.5.5`, `openclaw@2026.5.6`, and
171
+ `openclaw@2026.5.16-beta.2`.
171
172
  Keep the peer range broad unless an upstream release removes a runtime surface
172
173
  Remnic actively uses.
173
174
 
175
+ OpenClaw 2026.5.16 package-entry discovery prefers explicit built runtime
176
+ entries for installed packages. The published Remnic adapter declares
177
+ `openclaw.runtimeExtensions: ["./dist/index.js"]` alongside the existing
178
+ extension entry, and its package install metadata advertises
179
+ `openclaw.install.clawhubSpec: "clawhub:@remnic/plugin-openclaw"`,
180
+ `openclaw.install.npmSpec: "@remnic/plugin-openclaw"`, and
181
+ `openclaw.install.defaultChoice: "clawhub"`. That keeps OpenClaw setup,
182
+ repair, and update flows ClawHub-first while preserving npm as the fallback
183
+ install surface and `>=2026.5.16-beta.1` as the minimum supported host range.
184
+ The manifest also declares `activation.onStartup: false`, leaves provider
185
+ ownership to the host, exposes the optional plugin-mode OpenAI key through
186
+ `providerAuthChoices`, and keeps `providerAuthEnvVars.openai` as compatibility
187
+ metadata for OpenClaw's pre-runtime env-var auth probes.
188
+
174
189
  Native memory registrars are tracked separately in
175
190
  [`docs/plugins/openclaw-native-memory-registrars.md`](../../docs/plugins/openclaw-native-memory-registrars.md).
176
191
  That spike explains why Remnic currently uses `registerMemoryCapability()` as
@@ -254,7 +269,7 @@ private plugin state, transcript buffers, auth metadata, or artifact paths.
254
269
  | `session_start` / `session_end` hooks | Supported | 2026.3.22 |
255
270
  | `before_compaction` / `after_compaction` hooks | Supported | 2026.3.22 |
256
271
  | `before_reset` hook | Supported | 2026.4.10 |
257
- | `commands.list` runtime discovery | Supported | 2026.4.10 |
272
+ | `api.registerCommand()` runtime discovery | Supported | 2026.5.16 |
258
273
  | `api.resetSession()` (compaction reset) | Supported | 2026.3.22 |
259
274
  | Checkpoint saves before compaction | Supported | 2026.3.22 |
260
275
 
@@ -285,8 +300,8 @@ registered as the `before_prompt_build` hook timeout on OpenClaw versions with
285
300
  per-hook timeout support. Raise it if first-turn recall is timing out during
286
301
  slow startup; older OpenClaw versions ignore the extra hook option safely.
287
302
 
288
- Session-scoped recall controls are exposed through OpenClaw's command
289
- discovery surface:
303
+ Session-scoped recall controls are exposed through OpenClaw's
304
+ `api.registerCommand()` surface when it is available:
290
305
 
291
306
  - `remnic off` / `remnic on`
292
307
  - `remnic status`
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-5LE4HTVL.js";
4
4
  import {
5
5
  FallbackLlmClient
6
- } from "./chunk-7NUFIRM3.js";
6
+ } from "./chunk-VFULKFKI.js";
7
7
  import "./chunk-3A5ELHTT.js";
8
8
  import {
9
9
  log
@@ -5,7 +5,7 @@ import {
5
5
  import {
6
6
  buildExtensionsBlockForConsolidation,
7
7
  runPostConsolidationMaterialize
8
- } from "./chunk-NDZNURDM.js";
8
+ } from "./chunk-CMKR6NDQ.js";
9
9
  import "./chunk-WPINX4MF.js";
10
10
  import {
11
11
  isRecord
@@ -16,9 +16,9 @@ import {
16
16
  } from "./chunk-5LE4HTVL.js";
17
17
  import {
18
18
  FallbackLlmClient
19
- } from "./chunk-7NUFIRM3.js";
19
+ } from "./chunk-VFULKFKI.js";
20
20
  import "./chunk-3A5ELHTT.js";
21
- import "./chunk-ZXLYEVOP.js";
21
+ import "./chunk-EYCLXMIV.js";
22
22
  import "./chunk-6OJAU466.js";
23
23
  import "./chunk-RKR6PTPA.js";
24
24
  import {
@@ -5,7 +5,7 @@ import {
5
5
  import {
6
6
  StorageManager,
7
7
  isSemanticConsolidationLlmOperator
8
- } from "./chunk-ZXLYEVOP.js";
8
+ } from "./chunk-EYCLXMIV.js";
9
9
  import {
10
10
  log
11
11
  } from "./chunk-UFU5GGGA.js";
@@ -1674,6 +1674,103 @@ function applyContinuityLoopReview(existing, input, nowIso) {
1674
1674
  };
1675
1675
  }
1676
1676
 
1677
+ // ../remnic-core/src/utils/iso-timestamp.ts
1678
+ var ISO_UTC_TIMESTAMP_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;
1679
+ var ISO_OFFSET_TIMESTAMP_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
1680
+ var FLEXIBLE_ISO_TIMESTAMP_RE = /^(\d{4})-(\d{2})-(\d{2})(?:[Tt](\d{2}):(\d{2})(?::(\d{2})(?:\.\d{1,9})?)?(?:[Zz]|([+-])(\d{2}):(\d{2}))?)?$/;
1681
+ function isoDaysInMonth(year, month) {
1682
+ const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
1683
+ return [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month - 1] ?? 0;
1684
+ }
1685
+ function validateDateComponents(isoString) {
1686
+ const match = isoString.match(
1687
+ /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})/
1688
+ );
1689
+ if (!match) return false;
1690
+ const [, yStr, mStr, dStr, hStr, minStr, sStr] = match;
1691
+ const y = Number(yStr);
1692
+ const m = Number(mStr);
1693
+ const d = Number(dStr);
1694
+ const h = Number(hStr);
1695
+ const min = Number(minStr);
1696
+ const s = Number(sStr);
1697
+ if (m < 1 || m > 12) return false;
1698
+ if (d < 1 || d > 31) return false;
1699
+ if (h > 23 || min > 59 || s > 59) return false;
1700
+ if (d > isoDaysInMonth(y, m)) return false;
1701
+ return true;
1702
+ }
1703
+ function validateOffset(isoString) {
1704
+ const offsetMatch = isoString.match(/([+-])(\d{2}):(\d{2})$/);
1705
+ if (!offsetMatch) return true;
1706
+ const oh = Number(offsetMatch[2]);
1707
+ const om = Number(offsetMatch[3]);
1708
+ if (oh > 14 || om > 59) return false;
1709
+ if (oh === 14 && om > 0) return false;
1710
+ return true;
1711
+ }
1712
+ function normalizeUtcForComparison(value) {
1713
+ const fracMatch = value.match(/\.(\d+)Z$/);
1714
+ if (fracMatch) {
1715
+ const ms = (fracMatch[1] + "000").slice(0, 3);
1716
+ return value.replace(/\.\d+Z$/, `.${ms}Z`);
1717
+ }
1718
+ return value.replace(/Z$/, ".000Z");
1719
+ }
1720
+ function parseIsoUtcTimestamp(value) {
1721
+ if (typeof value !== "string" || !ISO_UTC_TIMESTAMP_RE.test(value)) {
1722
+ return null;
1723
+ }
1724
+ const ts = Date.parse(value);
1725
+ if (!Number.isFinite(ts)) return null;
1726
+ if (!validateDateComponents(value)) return null;
1727
+ const roundTrip = new Date(ts).toISOString();
1728
+ if (roundTrip !== normalizeUtcForComparison(value)) return null;
1729
+ return ts;
1730
+ }
1731
+ function parseIsoOffsetTimestamp(value) {
1732
+ if (typeof value !== "string" || !ISO_OFFSET_TIMESTAMP_RE.test(value)) {
1733
+ return null;
1734
+ }
1735
+ const ts = Date.parse(value);
1736
+ if (!Number.isFinite(ts)) return null;
1737
+ if (!validateDateComponents(value)) return null;
1738
+ if (!validateOffset(value)) return null;
1739
+ if (value.endsWith("Z")) {
1740
+ const roundTrip = new Date(ts).toISOString();
1741
+ if (roundTrip !== normalizeUtcForComparison(value)) return null;
1742
+ }
1743
+ return ts;
1744
+ }
1745
+ function parseFlexibleIsoTimestamp(value) {
1746
+ const match = typeof value === "string" ? value.match(FLEXIBLE_ISO_TIMESTAMP_RE) : null;
1747
+ if (!match) {
1748
+ return null;
1749
+ }
1750
+ const year = Number(match[1]);
1751
+ const month = Number(match[2]);
1752
+ const day = Number(match[3]);
1753
+ const hour = match[4] === void 0 ? 0 : Number(match[4]);
1754
+ const minute = match[5] === void 0 ? 0 : Number(match[5]);
1755
+ const second = match[6] === void 0 ? 0 : Number(match[6]);
1756
+ const offsetHour = match[8] === void 0 ? void 0 : Number(match[8]);
1757
+ const offsetMinute = match[9] === void 0 ? void 0 : Number(match[9]);
1758
+ const hasTime = match[4] !== void 0;
1759
+ const hasOffset = offsetHour !== void 0 && offsetMinute !== void 0;
1760
+ const hasTimezone = /(?:[Zz]|[+-]\d{2}:\d{2})$/.test(value);
1761
+ if (month < 1 || month > 12 || day < 1 || day > isoDaysInMonth(year, month) || hasTime && !hasTimezone) {
1762
+ return null;
1763
+ }
1764
+ if (hasTime && (hour > 23 || minute > 59 || second > 59)) {
1765
+ return null;
1766
+ }
1767
+ if (hasOffset && (offsetMinute > 59 || offsetHour > 14 || offsetHour === 14 && offsetMinute > 0)) {
1768
+ return null;
1769
+ }
1770
+ const ts = Date.parse(value);
1771
+ return Number.isFinite(ts) ? ts : null;
1772
+ }
1773
+
1677
1774
  // ../remnic-core/src/storage.ts
1678
1775
  var ARTIFACT_SEARCH_STOPWORDS = /* @__PURE__ */ new Set([
1679
1776
  "a",
@@ -1718,6 +1815,18 @@ function assertMemoryWorthCounter(field, value) {
1718
1815
  throw new Error(`${field} must be >= 0, got ${value}`);
1719
1816
  }
1720
1817
  }
1818
+ function normalizeMemoryWriteTimestamp(field, value) {
1819
+ if (value === void 0) return void 0;
1820
+ if (typeof value !== "string") {
1821
+ throw new Error(`${field} must be an ISO timestamp string, got ${String(value)}`);
1822
+ }
1823
+ const trimmed = value.trim();
1824
+ const parsed = parseFlexibleIsoTimestamp(trimmed);
1825
+ if (parsed === null) {
1826
+ throw new Error(`${field} must be a valid ISO timestamp, got ${JSON.stringify(value)}`);
1827
+ }
1828
+ return new Date(parsed).toISOString();
1829
+ }
1721
1830
  function isErrnoCode(error, code) {
1722
1831
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
1723
1832
  }
@@ -3729,6 +3838,7 @@ var StorageManager = class _StorageManager {
3729
3838
  const id = `${category}-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
3730
3839
  const conf = options.confidence ?? 0.8;
3731
3840
  const tier = confidenceTier(conf);
3841
+ const validAt = normalizeMemoryWriteTimestamp("validAt", options.validAt);
3732
3842
  let expiresAt;
3733
3843
  if (typeof options.expiresAt === "string" && options.expiresAt.length > 0) {
3734
3844
  expiresAt = options.expiresAt;
@@ -3758,6 +3868,7 @@ var StorageManager = class _StorageManager {
3758
3868
  sourceMemoryId: options.sourceMemoryId,
3759
3869
  sourceTurnId: options.sourceTurnId,
3760
3870
  memoryKind: options.memoryKind,
3871
+ valid_at: validAt,
3761
3872
  structuredAttributes: options.structuredAttributes
3762
3873
  };
3763
3874
  if (options.status !== void 0) {
@@ -6413,6 +6524,7 @@ ${memory.content}
6413
6524
  const id = `${parentId}-chunk-${chunkIndex}`;
6414
6525
  const conf = options.confidence ?? 0.8;
6415
6526
  const tier = confidenceTier(conf);
6527
+ const validAt = normalizeMemoryWriteTimestamp("validAt", options.validAt);
6416
6528
  const fm = {
6417
6529
  id,
6418
6530
  category,
@@ -6430,7 +6542,8 @@ ${memory.content}
6430
6542
  intentGoal: options.intentGoal,
6431
6543
  intentActionType: options.intentActionType,
6432
6544
  intentEntityTypes: options.intentEntityTypes,
6433
- memoryKind: options.memoryKind
6545
+ memoryKind: options.memoryKind,
6546
+ valid_at: validAt
6434
6547
  };
6435
6548
  const sanitized = sanitizeMemoryContent(content);
6436
6549
  if (!sanitized.clean) {
@@ -6728,6 +6841,9 @@ export {
6728
6841
  normalizeProjectionPreview,
6729
6842
  normalizeProjectionTags,
6730
6843
  parseContinuityImprovementLoops,
6844
+ parseIsoUtcTimestamp,
6845
+ parseIsoOffsetTimestamp,
6846
+ parseFlexibleIsoTimestamp,
6731
6847
  normalizeEntityName,
6732
6848
  ContentHashIndex,
6733
6849
  normalizeAttributePairs,
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-4G2XCSD2.js";
4
4
  import {
5
5
  StorageManager
6
- } from "./chunk-ZXLYEVOP.js";
6
+ } from "./chunk-EYCLXMIV.js";
7
7
 
8
8
  // ../remnic-core/src/maintenance/memory-governance.ts
9
9
  import path from "path";
@@ -5,7 +5,7 @@ import {
5
5
  StorageManager,
6
6
  parseContinuityImprovementLoops,
7
7
  sanitizeMemoryContent
8
- } from "./chunk-ZXLYEVOP.js";
8
+ } from "./chunk-EYCLXMIV.js";
9
9
  import {
10
10
  log
11
11
  } from "./chunk-UFU5GGGA.js";
@@ -842,7 +842,7 @@ var CompoundingEngine = class {
842
842
  let promotionCandidates = this.config.compoundingSemanticEnabled ? this.derivePromotionCandidates(outcomeSummary, mistakes.registry, rubrics) : [];
843
843
  if (this.config.cmcConsolidationEnabled) {
844
844
  try {
845
- const { deriveCausalPromotionCandidates, materializeAfterCausalConsolidation } = await import("./causal-consolidation-DSLFN64P.js");
845
+ const { deriveCausalPromotionCandidates, materializeAfterCausalConsolidation } = await import("./causal-consolidation-C64NNE4T.js");
846
846
  const causalCandidates = await deriveCausalPromotionCandidates({
847
847
  memoryDir: this.config.memoryDir,
848
848
  causalTrajectoryStoreDir: this.config.causalTrajectoryStoreDir,
@@ -875,7 +875,7 @@ var CompoundingEngine = class {
875
875
  }
876
876
  if (this.config.calibrationEnabled) {
877
877
  try {
878
- const { runCalibrationConsolidation } = await import("./calibration-JD4AU7FB.js");
878
+ const { runCalibrationConsolidation } = await import("./calibration-Z5WWNV7U.js");
879
879
  const calRules = await runCalibrationConsolidation({
880
880
  memoryDir: this.config.memoryDir,
881
881
  gatewayConfig: this.config.gatewayConfig,
@@ -66,6 +66,11 @@ var _resolverLoaded = false;
66
66
  var _resolverNextRetryAt = 0;
67
67
  var RESOLVER_RETRY_BACKOFF_MS = 6e4;
68
68
  var resolvedCache = /* @__PURE__ */ new Map();
69
+ var NON_LITERAL_AUTH_MARKERS = /* @__PURE__ */ new Set([
70
+ "secretref-managed",
71
+ "lm-studio"
72
+ ]);
73
+ var ENV_VAR_MARKER_RE = /^[A-Z][A-Z0-9_]*(?:_API_KEY|_ACCESS_TOKEN|_TOKEN|_SECRET|_CREDENTIALS|_CREDENTIALS_JSON)$/;
69
74
  async function getGatewayResolver() {
70
75
  if (_resolverLoaded) {
71
76
  return _resolveCredentialForProvider;
@@ -178,6 +183,14 @@ function findExecutableOnPath(executableName, access, stat, executableMode) {
178
183
  }
179
184
  return void 0;
180
185
  }
186
+ function isNonLiteralAuthMarker(value) {
187
+ return NON_LITERAL_AUTH_MARKERS.has(value) || value.endsWith("-oauth") || value.endsWith("-local") || value.startsWith("gcp-") || ENV_VAR_MARKER_RE.test(value);
188
+ }
189
+ function resolveFromNamedEnvVar(marker) {
190
+ if (!ENV_VAR_MARKER_RE.test(marker)) return void 0;
191
+ const value = readEnvVar(marker);
192
+ return value && value.trim().length > 0 ? value.trim() : void 0;
193
+ }
181
194
  async function resolveProviderCredential(providerId, credentialValue, gatewayConfig, agentDir) {
182
195
  const resolvedAgentDir = path.resolve(
183
196
  agentDir ?? path.join(os.homedir(), ".openclaw", "agents", "main", "agent")
@@ -188,9 +201,16 @@ async function resolveProviderCredential(providerId, credentialValue, gatewayCon
188
201
  }
189
202
  let resolved;
190
203
  if (typeof credentialValue === "string" && credentialValue.trim().length > 0) {
191
- if (credentialValue === "secretref-managed" || credentialValue.endsWith("-oauth") || credentialValue.endsWith("-local") || credentialValue === "lm-studio" || credentialValue.startsWith("gcp-")) {
204
+ const trimmedCredentialValue = credentialValue.trim();
205
+ if (isNonLiteralAuthMarker(trimmedCredentialValue)) {
206
+ const markerEnvValue = resolveFromNamedEnvVar(trimmedCredentialValue);
207
+ if (markerEnvValue) {
208
+ resolved = markerEnvValue;
209
+ resolvedCache.set(cacheKey, resolved);
210
+ return resolved;
211
+ }
192
212
  } else {
193
- resolved = credentialValue;
213
+ resolved = trimmedCredentialValue;
194
214
  resolvedCache.set(cacheKey, resolved);
195
215
  return resolved;
196
216
  }
@@ -307,7 +327,8 @@ function normalizeCodexCliFallbackConfig(config) {
307
327
  }
308
328
  function normalizeCodexCliFallbackOptions(options) {
309
329
  return {
310
- ...options.timeoutMs !== void 0 ? { timeoutMs: normalizeCodexCliTimeoutMs(options.timeoutMs) } : {}
330
+ ...options.timeoutMs !== void 0 ? { timeoutMs: normalizeCodexCliTimeoutMs(options.timeoutMs) } : {},
331
+ ...options.signal ? { signal: options.signal } : {}
311
332
  };
312
333
  }
313
334
  function normalizeOptionalString(value, label) {
@@ -386,12 +407,15 @@ var FallbackLlmClient = class {
386
407
  log.warn("fallback LLM: no models configured in gateway");
387
408
  return null;
388
409
  }
389
- const runChain = async () => {
410
+ const runChain = async (runOptions) => {
390
411
  for (let i = 0; i < models.length; i++) {
412
+ if (runOptions.signal?.aborted) {
413
+ throw abortReason(runOptions.signal);
414
+ }
391
415
  const model = models[i];
392
416
  const isFallback = i > 0;
393
417
  try {
394
- const result = await this.tryModel(model, messages, options);
418
+ const result = await this.tryModel(model, messages, runOptions);
395
419
  if (result) {
396
420
  if (isFallback) {
397
421
  log.debug(`fallback LLM: succeeded using ${model.modelString} (fallback ${i})`);
@@ -403,6 +427,9 @@ var FallbackLlmClient = class {
403
427
  };
404
428
  }
405
429
  } catch (err) {
430
+ if (runOptions.signal?.aborted) {
431
+ throw abortReason(runOptions.signal);
432
+ }
406
433
  const errorMsg = err instanceof Error ? err.message : String(err);
407
434
  log.debug(`fallback LLM: ${model.modelString} failed (${errorMsg}), trying next...`);
408
435
  }
@@ -416,21 +443,37 @@ var FallbackLlmClient = class {
416
443
  return null;
417
444
  }
418
445
  let timeoutHandle;
446
+ const controller = new AbortController();
447
+ const onCallerAbort = () => {
448
+ controller.abort(abortReason(options.signal));
449
+ };
450
+ options.signal?.addEventListener("abort", onCallerAbort, { once: true });
451
+ if (options.signal?.aborted) {
452
+ onCallerAbort();
453
+ }
454
+ const timedOptions = { ...options, signal: controller.signal };
455
+ const chain = runChain(timedOptions);
456
+ chain.catch(() => {
457
+ });
419
458
  try {
420
459
  return await Promise.race([
421
- runChain(),
460
+ chain,
422
461
  new Promise((resolve) => {
423
462
  timeoutHandle = setTimeout(() => {
424
463
  log.warn(`fallback LLM: timed out after ${options.timeoutMs}ms`);
464
+ controller.abort(
465
+ new Error(`fallback LLM timed out after ${options.timeoutMs}ms`)
466
+ );
425
467
  resolve(null);
426
468
  }, options.timeoutMs);
427
469
  })
428
470
  ]);
429
471
  } finally {
430
472
  if (timeoutHandle) clearTimeout(timeoutHandle);
473
+ options.signal?.removeEventListener("abort", onCallerAbort);
431
474
  }
432
475
  }
433
- return await runChain();
476
+ return await runChain(options);
434
477
  }
435
478
  /**
436
479
  * Make a request with structured output (Zod schema).
@@ -618,7 +661,7 @@ var FallbackLlmClient = class {
618
661
  effectiveConfig,
619
662
  model.modelId,
620
663
  messages,
621
- { timeoutMs: options.timeoutMs }
664
+ { timeoutMs: options.timeoutMs, signal: options.signal }
622
665
  );
623
666
  }
624
667
  if (model.providerConfig.api === "ollama-chat") {
@@ -712,6 +755,7 @@ var FallbackLlmClient = class {
712
755
  const response = await fetch(url, {
713
756
  method: "POST",
714
757
  headers,
758
+ signal: options.signal,
715
759
  body: JSON.stringify(body)
716
760
  });
717
761
  if (!response.ok) {
@@ -750,6 +794,7 @@ var FallbackLlmClient = class {
750
794
  const response = await fetch(url, {
751
795
  method: "POST",
752
796
  headers,
797
+ signal: options.signal,
753
798
  body: JSON.stringify({
754
799
  model: modelId,
755
800
  messages,
@@ -816,6 +861,7 @@ var FallbackLlmClient = class {
816
861
  const response = await fetch(url, {
817
862
  method: "POST",
818
863
  headers,
864
+ signal: options.signal,
819
865
  body: JSON.stringify(body)
820
866
  });
821
867
  if (!response.ok) {
@@ -868,6 +914,7 @@ var FallbackLlmClient = class {
868
914
  const response = await fetch(url, {
869
915
  method: "POST",
870
916
  headers,
917
+ signal: options.signal,
871
918
  body: JSON.stringify(body)
872
919
  });
873
920
  if (!response.ok) {
@@ -889,6 +936,10 @@ var FallbackLlmClient = class {
889
936
  };
890
937
  }
891
938
  };
939
+ function abortReason(signal) {
940
+ const reason = signal?.reason;
941
+ return reason instanceof Error ? reason : new Error("fallback LLM request aborted");
942
+ }
892
943
  function normalizeRuntimePath(value) {
893
944
  if (typeof value !== "string") return void 0;
894
945
  const trimmed = value.trim();
@@ -135,7 +135,8 @@ async function callLlm(client, config, userMessage) {
135
135
  if ("chatCompletion" in client && typeof client.chatCompletion === "function") {
136
136
  const result = await client.chatCompletion(messages, {
137
137
  temperature: 0.1,
138
- maxTokens: 4096
138
+ maxTokens: 4096,
139
+ operation: "contradiction-judge"
139
140
  });
140
141
  return result?.content ?? "";
141
142
  }
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  CompoundingEngine,
3
3
  defaultTierMigrationCycleBudget
4
- } from "./chunk-7NMHI4IC.js";
4
+ } from "./chunk-UTDLHBBV.js";
5
5
  import "./chunk-EXDYWXMB.js";
6
- import "./chunk-ZXLYEVOP.js";
6
+ import "./chunk-EYCLXMIV.js";
7
7
  import "./chunk-6OJAU466.js";
8
8
  import "./chunk-RKR6PTPA.js";
9
9
  import "./chunk-UFU5GGGA.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  FallbackLlmClient
3
- } from "./chunk-7NUFIRM3.js";
3
+ } from "./chunk-VFULKFKI.js";
4
4
  import "./chunk-3A5ELHTT.js";
5
5
  import "./chunk-UFU5GGGA.js";
6
6
  import "./chunk-TDRJVMUP.js";