@remnic/core 9.3.714 → 9.3.716
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-boundary.d.ts +2 -2
- package/dist/access-cli.js +2 -2
- package/dist/access-http.d.ts +2 -2
- package/dist/access-mcp.d.ts +2 -2
- package/dist/access-operations.d.ts +2 -2
- package/dist/{access-service-lddjZoRh.d.ts → access-service-BW8cm_uU.d.ts} +1 -1
- package/dist/access-service.d.ts +2 -2
- package/dist/access-surface-catalog.d.ts +2 -2
- package/dist/bootstrap.d.ts +1 -1
- package/dist/{chunk-OQH5XUH3.js → chunk-Q6IP2ARR.js} +2 -5
- package/dist/chunk-Q6IP2ARR.js.map +1 -0
- package/dist/{chunk-FXZKXW7C.js → chunk-SQ4LU27I.js} +48 -20
- package/dist/chunk-SQ4LU27I.js.map +1 -0
- package/dist/{cli-iAiVkbYJ.d.ts → cli-Dpivy32W.d.ts} +2 -2
- package/dist/cli.d.ts +3 -3
- package/dist/direct-answer-wiring.d.ts +9 -9
- package/dist/direct-answer-wiring.js +1 -1
- package/dist/explicit-capture.d.ts +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -2
- package/dist/mcp-memory-inspector-app.d.ts +2 -2
- package/dist/{orchestrator-iVZWi8aW.d.ts → orchestrator-vygo2YIM.d.ts} +15 -5
- package/dist/orchestrator.d.ts +1 -1
- package/dist/orchestrator.js +2 -2
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-planner-llm.js +2 -2
- package/dist/recall-planner-llm.js.map +1 -1
- package/dist/schemas.d.ts +22 -22
- package/dist/transfer/types.d.ts +12 -12
- package/package.json +2 -2
- package/src/capabilities.test.ts +28 -0
- package/src/direct-answer-wiring.test.ts +20 -13
- package/src/direct-answer-wiring.ts +9 -13
- package/src/faithfulness-graph-guard.test.ts +214 -8
- package/src/orchestrator.ts +68 -27
- package/src/recall-planner-llm.test.ts +30 -11
- package/src/recall-planner-llm.ts +4 -5
- package/dist/chunk-FXZKXW7C.js.map +0 -1
- package/dist/chunk-OQH5XUH3.js.map +0 -1
package/src/orchestrator.ts
CHANGED
|
@@ -1396,12 +1396,11 @@ export async function resolveRecallModeDecisionAsync(
|
|
|
1396
1396
|
options: RecallModeGraphOptions & {
|
|
1397
1397
|
config: PluginConfig;
|
|
1398
1398
|
/**
|
|
1399
|
-
* Recall-operation capability gates (issue #1523).
|
|
1400
|
-
*
|
|
1401
|
-
*
|
|
1402
|
-
* planner gate falls back to `config.recallPlannerLlmEnabled` when omitted.
|
|
1399
|
+
* Recall-operation capability gates (issue #1523). REQUIRED: the recall
|
|
1400
|
+
* orchestrator always passes a resolved set — the LLM planner gate reads
|
|
1401
|
+
* `caps.recallPlannerLlm`, never re-derives from config.
|
|
1403
1402
|
*/
|
|
1404
|
-
caps
|
|
1403
|
+
caps: CapabilitySet;
|
|
1405
1404
|
hints?: string[];
|
|
1406
1405
|
llm?: FallbackLlmClient;
|
|
1407
1406
|
signal?: AbortSignal;
|
|
@@ -1410,10 +1409,8 @@ export async function resolveRecallModeDecisionAsync(
|
|
|
1410
1409
|
const heuristicDecision = resolveRecallModeDecision(options);
|
|
1411
1410
|
|
|
1412
1411
|
// Planner globally off, or LLM planning not opted into → heuristic only.
|
|
1413
|
-
//
|
|
1414
|
-
|
|
1415
|
-
const plannerLlmEnabled =
|
|
1416
|
-
options.caps?.recallPlannerLlm ?? options.config.recallPlannerLlmEnabled;
|
|
1412
|
+
// Read the resolved capability (issue #1523) — never re-derive from config.
|
|
1413
|
+
const plannerLlmEnabled = options.caps.recallPlannerLlm;
|
|
1417
1414
|
if (!options.plannerEnabled || !plannerLlmEnabled) {
|
|
1418
1415
|
return heuristicDecision;
|
|
1419
1416
|
}
|
|
@@ -1423,9 +1420,9 @@ export async function resolveRecallModeDecisionAsync(
|
|
|
1423
1420
|
options.prompt,
|
|
1424
1421
|
options.hints,
|
|
1425
1422
|
options.config,
|
|
1423
|
+
options.caps,
|
|
1426
1424
|
options.llm,
|
|
1427
1425
|
options.signal,
|
|
1428
|
-
options.caps,
|
|
1429
1426
|
);
|
|
1430
1427
|
|
|
1431
1428
|
// Shadow mode: record what the LLM would have chosen but keep the heuristic
|
|
@@ -1731,8 +1728,16 @@ export function resolveRecentThreadMemoryPaths(options: {
|
|
|
1731
1728
|
buildMemoryPathById(options.allMemsForGraph, options.storageDir);
|
|
1732
1729
|
if (pathById.size === 0) return [];
|
|
1733
1730
|
|
|
1731
|
+
// #1635 (defensive): skip pending_review ids from legacy episode sets.
|
|
1732
|
+
const pendingReviewIds = new Set<string>(
|
|
1733
|
+
(options.allMemsForGraph ?? [])
|
|
1734
|
+
.filter((m) => m.frontmatter.status === "pending_review" && m.frontmatter.id)
|
|
1735
|
+
.map((m) => m.frontmatter.id as string),
|
|
1736
|
+
);
|
|
1737
|
+
|
|
1734
1738
|
return options.threadEpisodeIds
|
|
1735
1739
|
.filter((id) => id !== options.currentMemoryId)
|
|
1740
|
+
.filter((id) => !pendingReviewIds.has(id))
|
|
1736
1741
|
.slice(-maxRecent)
|
|
1737
1742
|
.map((id) => pathById.get(id))
|
|
1738
1743
|
.filter((p): p is string => typeof p === "string" && p.length > 0);
|
|
@@ -1858,6 +1863,11 @@ export class Orchestrator {
|
|
|
1858
1863
|
* callers already destructure `persistedIds` by position.
|
|
1859
1864
|
*/
|
|
1860
1865
|
private lastPersistExtractionDeferredCount: number = 0;
|
|
1866
|
+
/**
|
|
1867
|
+
* Side-channel (#1635): pending_review persisted ids from the last
|
|
1868
|
+
* persistExtraction; runExtraction excludes them from the thread episode set.
|
|
1869
|
+
*/
|
|
1870
|
+
private lastPersistExtractionPendingReviewIds: string[] = [];
|
|
1861
1871
|
private readonly _fastGatewayLlm: FallbackLlmClient | null;
|
|
1862
1872
|
|
|
1863
1873
|
get fastGatewayLlm(): FallbackLlmClient | null {
|
|
@@ -13461,23 +13471,16 @@ export class Orchestrator {
|
|
|
13461
13471
|
}
|
|
13462
13472
|
|
|
13463
13473
|
// Batch-append persisted IDs so non-fact memories (entities/questions) are
|
|
13464
|
-
// always attached to the thread.
|
|
13474
|
+
// always attached to the thread. The helper excludes pending_review ids (#1635).
|
|
13465
13475
|
if (
|
|
13466
13476
|
this.config.threadingEnabled &&
|
|
13467
13477
|
threadIdForExtraction &&
|
|
13468
13478
|
persistedIds.length > 0
|
|
13469
13479
|
) {
|
|
13470
|
-
|
|
13471
|
-
|
|
13472
|
-
|
|
13473
|
-
|
|
13474
|
-
);
|
|
13475
|
-
} catch (err) {
|
|
13476
|
-
log.warn(
|
|
13477
|
-
"[threading] appendEpisodeIds failed after persistence (non-fatal)",
|
|
13478
|
-
err,
|
|
13479
|
-
);
|
|
13480
|
-
}
|
|
13480
|
+
await this.appendPersistedThreadEpisodes(
|
|
13481
|
+
threadIdForExtraction,
|
|
13482
|
+
persistedIds,
|
|
13483
|
+
);
|
|
13481
13484
|
}
|
|
13482
13485
|
|
|
13483
13486
|
// Thread title update for the already-established thread context.
|
|
@@ -13831,9 +13834,11 @@ export class Orchestrator {
|
|
|
13831
13834
|
// to avoid a maintenance hazard where the two guard paths could diverge.
|
|
13832
13835
|
return attachCitation(content, citationContext, citationTemplate);
|
|
13833
13836
|
};
|
|
13837
|
+
const persistedIds: string[] = [];
|
|
13834
13838
|
const supersessionOrderingAt = (validAt?: string): string =>
|
|
13835
13839
|
validAt && validAt.length > 0 ? validAt : new Date().toISOString();
|
|
13836
|
-
|
|
13840
|
+
// #1635: pending_review persisted ids, excluded from the thread episode set below.
|
|
13841
|
+
const pendingReviewPersistedIds: string[] = [];
|
|
13837
13842
|
const persistedIdsByStorage = new Map<
|
|
13838
13843
|
string,
|
|
13839
13844
|
{ storage: StorageManager; ids: string[] }
|
|
@@ -13841,11 +13846,18 @@ export class Orchestrator {
|
|
|
13841
13846
|
const trackPersistedId = (
|
|
13842
13847
|
targetStorage: StorageManager,
|
|
13843
13848
|
id: string,
|
|
13844
|
-
options: {
|
|
13849
|
+
options: {
|
|
13850
|
+
includeReturnedIds?: boolean;
|
|
13851
|
+
/** #1635: keep this id out of the persisted thread episode set. */
|
|
13852
|
+
pendingReview?: boolean;
|
|
13853
|
+
} = {},
|
|
13845
13854
|
): void => {
|
|
13846
13855
|
if (options.includeReturnedIds !== false) {
|
|
13847
13856
|
persistedIds.push(id);
|
|
13848
13857
|
}
|
|
13858
|
+
if (options.pendingReview) {
|
|
13859
|
+
pendingReviewPersistedIds.push(id);
|
|
13860
|
+
}
|
|
13849
13861
|
const key = targetStorage.dir;
|
|
13850
13862
|
const existing = persistedIdsByStorage.get(key);
|
|
13851
13863
|
if (existing) {
|
|
@@ -15347,7 +15359,9 @@ export class Orchestrator {
|
|
|
15347
15359
|
log.debug(
|
|
15348
15360
|
`chunked memory ${parentId} into ${chunkResult.chunks.length} chunks`,
|
|
15349
15361
|
);
|
|
15350
|
-
trackPersistedId(targetStorage, parentId
|
|
15362
|
+
trackPersistedId(targetStorage, parentId, {
|
|
15363
|
+
pendingReview: faithfulnessEnforceStatus === "pending_review",
|
|
15364
|
+
});
|
|
15351
15365
|
// #1576 (cursor Medium): keep pending_review ids out of threadEpisodeIdsForGraph — else later active facts build thread-predecessor edges to an unfaithful memory.
|
|
15352
15366
|
if (
|
|
15353
15367
|
faithfulnessEnforceStatus !== "pending_review" &&
|
|
@@ -15633,8 +15647,9 @@ export class Orchestrator {
|
|
|
15633
15647
|
source: "extraction",
|
|
15634
15648
|
}),
|
|
15635
15649
|
);
|
|
15636
|
-
trackPersistedId(targetStorage, memoryId
|
|
15637
|
-
|
|
15650
|
+
trackPersistedId(targetStorage, memoryId, {
|
|
15651
|
+
pendingReview: faithfulnessEnforceStatus === "pending_review",
|
|
15652
|
+
});
|
|
15638
15653
|
if (
|
|
15639
15654
|
faithfulnessEnforceStatus !== "pending_review" &&
|
|
15640
15655
|
threadEpisodeIdsForGraph &&
|
|
@@ -15944,9 +15959,35 @@ export class Orchestrator {
|
|
|
15944
15959
|
log.debug(`temporal-index update error (non-fatal): ${err}`),
|
|
15945
15960
|
);
|
|
15946
15961
|
|
|
15962
|
+
// #1635: surface pending_review ids so the thread episode set excludes them.
|
|
15963
|
+
this.lastPersistExtractionPendingReviewIds = pendingReviewPersistedIds;
|
|
15947
15964
|
// Return the persisted fact IDs for threading
|
|
15948
15965
|
return persistedIds;
|
|
15949
15966
|
}
|
|
15967
|
+
/**
|
|
15968
|
+
* Append persisted ids to the thread episode set, excluding pending_review
|
|
15969
|
+
* fact ids (#1635) so they don't re-seed predecessor edges. Fail-open like
|
|
15970
|
+
* the raw appendEpisodeIds call it replaces.
|
|
15971
|
+
*/
|
|
15972
|
+
private async appendPersistedThreadEpisodes(
|
|
15973
|
+
threadId: string,
|
|
15974
|
+
persistedIds: string[],
|
|
15975
|
+
): Promise<void> {
|
|
15976
|
+
const pendingReviewIds = this.lastPersistExtractionPendingReviewIds ?? [];
|
|
15977
|
+
const episodeIds =
|
|
15978
|
+
pendingReviewIds.length > 0
|
|
15979
|
+
? persistedIds.filter((id) => !pendingReviewIds.includes(id))
|
|
15980
|
+
: persistedIds;
|
|
15981
|
+
if (episodeIds.length === 0) return;
|
|
15982
|
+
try {
|
|
15983
|
+
await this.threading.appendEpisodeIds(threadId, episodeIds);
|
|
15984
|
+
} catch (err) {
|
|
15985
|
+
log.warn(
|
|
15986
|
+
"[threading] appendEpisodeIds failed after persistence (non-fatal)",
|
|
15987
|
+
err,
|
|
15988
|
+
);
|
|
15989
|
+
}
|
|
15990
|
+
}
|
|
15950
15991
|
|
|
15951
15992
|
private async indexPersistedMemory(
|
|
15952
15993
|
storage: StorageManager,
|
|
@@ -41,7 +41,7 @@ test("returns heuristic without calling the LLM when recallPlannerLlmEnabled is
|
|
|
41
41
|
const captured: Array<Record<string, unknown>> = [];
|
|
42
42
|
const llm = stubLlm({ capturedOptions: captured, result: { mode: "no_recall" } });
|
|
43
43
|
|
|
44
|
-
const result = await planRecallModeLLM("what did we decide about auth?", undefined, config, llm, undefined
|
|
44
|
+
const result = await planRecallModeLLM("what did we decide about auth?", undefined, config, resolveCapabilities(config), llm, undefined);
|
|
45
45
|
|
|
46
46
|
assert.equal(captured.length, 0, "LLM must not be contacted when disabled");
|
|
47
47
|
assert.equal(result.source, "heuristic");
|
|
@@ -55,7 +55,7 @@ test("uses the LLM classification when enabled", async () => {
|
|
|
55
55
|
const config = parseConfig({ recallPlannerLlmEnabled: true });
|
|
56
56
|
const llm = stubLlm({ result: { mode: "graph_mode", reason: "asks for root cause" }, modelUsed: "anthropic/claude" });
|
|
57
57
|
|
|
58
|
-
const result = await planRecallModeLLM("restart the gateway", undefined, config, llm, undefined
|
|
58
|
+
const result = await planRecallModeLLM("restart the gateway", undefined, config, resolveCapabilities(config), llm, undefined);
|
|
59
59
|
|
|
60
60
|
assert.equal(result.source, "llm");
|
|
61
61
|
assert.equal(result.mode, "graph_mode");
|
|
@@ -75,7 +75,7 @@ test("forwards taskModelChain AND recallPlannerModel in gateway mode (provider-a
|
|
|
75
75
|
const captured: Array<Record<string, unknown>> = [];
|
|
76
76
|
const llm = stubLlm({ capturedOptions: captured, result: { mode: "minimal" } });
|
|
77
77
|
|
|
78
|
-
await planRecallModeLLM("check status", undefined, config, llm, undefined
|
|
78
|
+
await planRecallModeLLM("check status", undefined, config, resolveCapabilities(config), llm, undefined);
|
|
79
79
|
|
|
80
80
|
assert.equal(captured.length, 1);
|
|
81
81
|
// recallPlannerModel is tried first (prepended), taskModelChain is the fallback chain.
|
|
@@ -98,7 +98,7 @@ test("plugin mode passes only the explicit model, no gateway chain", async () =>
|
|
|
98
98
|
const captured: Array<Record<string, unknown>> = [];
|
|
99
99
|
const llm = stubLlm({ capturedOptions: captured, result: { mode: "full" } });
|
|
100
100
|
|
|
101
|
-
await planRecallModeLLM("summarize the project", undefined, config, llm, undefined
|
|
101
|
+
await planRecallModeLLM("summarize the project", undefined, config, resolveCapabilities(config), llm, undefined);
|
|
102
102
|
|
|
103
103
|
assert.equal(captured.length, 1);
|
|
104
104
|
assert.equal(captured[0]?.model, "openai/gpt-5.5");
|
|
@@ -110,7 +110,7 @@ test("falls back to heuristic when the LLM throws", async () => {
|
|
|
110
110
|
const config = parseConfig({ recallPlannerLlmEnabled: true });
|
|
111
111
|
const llm = stubLlm({ throwError: "boom" });
|
|
112
112
|
|
|
113
|
-
const result = await planRecallModeLLM("what happened during the outage?", undefined, config, llm, undefined
|
|
113
|
+
const result = await planRecallModeLLM("what happened during the outage?", undefined, config, resolveCapabilities(config), llm, undefined);
|
|
114
114
|
|
|
115
115
|
assert.equal(result.source, "heuristic-fallback");
|
|
116
116
|
assert.equal(result.fallbackUsed, true);
|
|
@@ -124,7 +124,7 @@ test("falls back to heuristic when the LLM returns no parseable result", async (
|
|
|
124
124
|
const config = parseConfig({ recallPlannerLlmEnabled: true });
|
|
125
125
|
const llm = stubLlm({ result: null });
|
|
126
126
|
|
|
127
|
-
const result = await planRecallModeLLM("how did we get here?", undefined, config, llm, undefined
|
|
127
|
+
const result = await planRecallModeLLM("how did we get here?", undefined, config, resolveCapabilities(config), llm, undefined);
|
|
128
128
|
|
|
129
129
|
assert.equal(result.source, "heuristic-fallback");
|
|
130
130
|
assert.equal(result.fallbackUsed, true);
|
|
@@ -140,7 +140,7 @@ test("falls back without a network attempt when the chain is empty and the model
|
|
|
140
140
|
const captured: Array<Record<string, unknown>> = [];
|
|
141
141
|
const llm = stubLlm({ available: false, capturedOptions: captured, result: { mode: "full" } });
|
|
142
142
|
|
|
143
|
-
const result = await planRecallModeLLM("anything", undefined, config, llm, undefined
|
|
143
|
+
const result = await planRecallModeLLM("anything", undefined, config, resolveCapabilities(config), llm, undefined);
|
|
144
144
|
|
|
145
145
|
assert.equal(captured.length, 0, "no network attempt when nothing is routable");
|
|
146
146
|
assert.equal(result.source, "heuristic-fallback");
|
|
@@ -156,7 +156,7 @@ test("attempts the call (and falls back) when a provider-qualified model overrid
|
|
|
156
156
|
const captured: Array<Record<string, unknown>> = [];
|
|
157
157
|
const llm = stubLlm({ available: false, capturedOptions: captured, result: null });
|
|
158
158
|
|
|
159
|
-
const result = await planRecallModeLLM("anything", undefined, config, llm, undefined
|
|
159
|
+
const result = await planRecallModeLLM("anything", undefined, config, resolveCapabilities(config), llm, undefined);
|
|
160
160
|
|
|
161
161
|
assert.equal(captured.length, 1, "qualified model override → still attempt the call");
|
|
162
162
|
assert.equal(captured[0]?.model, "openai/gpt-5.5");
|
|
@@ -171,7 +171,7 @@ test("an already-aborted recall short-circuits to the heuristic without an LLM c
|
|
|
171
171
|
const ac = new AbortController();
|
|
172
172
|
ac.abort();
|
|
173
173
|
|
|
174
|
-
const result = await planRecallModeLLM("what did we decide?", undefined, config, llm, ac.signal
|
|
174
|
+
const result = await planRecallModeLLM("what did we decide?", undefined, config, resolveCapabilities(config), llm, ac.signal);
|
|
175
175
|
|
|
176
176
|
assert.equal(captured.length, 0, "no LLM call when the recall is already aborted");
|
|
177
177
|
assert.equal(result.source, "heuristic-fallback");
|
|
@@ -185,7 +185,7 @@ test("forwards the abort signal into the LLM call (cancellation contract)", asyn
|
|
|
185
185
|
const llm = stubLlm({ capturedOptions: captured, result: { mode: "minimal" } });
|
|
186
186
|
const ac = new AbortController();
|
|
187
187
|
|
|
188
|
-
await planRecallModeLLM("check status", undefined, config, llm, ac.signal
|
|
188
|
+
await planRecallModeLLM("check status", undefined, config, resolveCapabilities(config), llm, ac.signal);
|
|
189
189
|
|
|
190
190
|
assert.equal(captured.length, 1);
|
|
191
191
|
assert.equal(captured[0]?.signal, ac.signal, "recall abort signal must reach FallbackLlmClient");
|
|
@@ -196,7 +196,7 @@ test("empty prompts skip the LLM entirely", async () => {
|
|
|
196
196
|
const captured: Array<Record<string, unknown>> = [];
|
|
197
197
|
const llm = stubLlm({ capturedOptions: captured, result: { mode: "full" } });
|
|
198
198
|
|
|
199
|
-
const result = await planRecallModeLLM(" ", undefined, config, llm, undefined
|
|
199
|
+
const result = await planRecallModeLLM(" ", undefined, config, resolveCapabilities(config), llm, undefined);
|
|
200
200
|
|
|
201
201
|
assert.equal(captured.length, 0);
|
|
202
202
|
assert.equal(result.mode, "no_recall"); // heuristic returns no_recall for empty
|
|
@@ -223,3 +223,22 @@ test("resolveRecallPlannerLlmOptions drops bare model names but keeps provider-q
|
|
|
223
223
|
);
|
|
224
224
|
assert.equal(qualified.model, "anthropic/claude-haiku-4-5");
|
|
225
225
|
});
|
|
226
|
+
|
|
227
|
+
test("caps.recallPlannerLlm is authoritative — disabled caps wins over enabled config flag (one-resolution-per-op #1523)", async () => {
|
|
228
|
+
// Issue #1523 contract: the capability resolved at the operation entry is
|
|
229
|
+
// THE gate. Construct a deliberate mismatch: config says enabled, but the
|
|
230
|
+
// resolved CapabilitySet says disabled. The function must NOT call the LLM.
|
|
231
|
+
const config = parseConfig({ recallPlannerLlmEnabled: true });
|
|
232
|
+
const captured: Array<Record<string, unknown>> = [];
|
|
233
|
+
const llm = stubLlm({ capturedOptions: captured, result: { mode: "full" } });
|
|
234
|
+
|
|
235
|
+
// Manually construct caps with recallPlannerLlm: false (simulating a
|
|
236
|
+
// resolution that disagrees with the raw config — e.g. session toggle off).
|
|
237
|
+
const caps = { ...resolveCapabilities(config), recallPlannerLlm: false } as any;
|
|
238
|
+
|
|
239
|
+
const result = await planRecallModeLLM("what did we decide?", undefined, config, caps, llm, undefined);
|
|
240
|
+
|
|
241
|
+
assert.equal(captured.length, 0, "LLM must not be contacted when caps says disabled, even if config says enabled");
|
|
242
|
+
assert.equal(result.source, "heuristic");
|
|
243
|
+
assert.equal(result.reason, "llm-disabled");
|
|
244
|
+
});
|
|
@@ -188,16 +188,15 @@ export async function planRecallModeLLM(
|
|
|
188
188
|
prompt: string,
|
|
189
189
|
hints: string[] | undefined,
|
|
190
190
|
config: PluginConfig,
|
|
191
|
+
caps: CapabilitySet,
|
|
191
192
|
llm?: FallbackLlmClient,
|
|
192
193
|
signal?: AbortSignal,
|
|
193
|
-
caps?: CapabilitySet,
|
|
194
194
|
): Promise<RecallPlannerLlmResult> {
|
|
195
195
|
const heuristicMode = planRecallMode(prompt);
|
|
196
196
|
|
|
197
|
-
//
|
|
198
|
-
// capability
|
|
199
|
-
|
|
200
|
-
const plannerLlmEnabled = caps?.recallPlannerLlm ?? config.recallPlannerLlmEnabled;
|
|
197
|
+
// The recall-operation entry resolved this gate once (issue #1523); read the
|
|
198
|
+
// capability, never re-derive it from raw config here.
|
|
199
|
+
const plannerLlmEnabled = caps.recallPlannerLlm;
|
|
201
200
|
if (!plannerLlmEnabled) {
|
|
202
201
|
return heuristicResult(heuristicMode, "heuristic", "llm-disabled", 0, false);
|
|
203
202
|
}
|