@remnic/core 9.3.715 → 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-GgnA--CE.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-YUJL27ET.js → chunk-SQ4LU27I.js} +5 -5
- package/dist/chunk-SQ4LU27I.js.map +1 -0
- package/dist/{cli-Do1YtfnH.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-BOaVstFG.d.ts → orchestrator-vygo2YIM.d.ts} +4 -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/orchestrator.ts +7 -10
- package/src/recall-planner-llm.test.ts +30 -11
- package/src/recall-planner-llm.ts +4 -5
- package/dist/chunk-OQH5XUH3.js.map +0 -1
- package/dist/chunk-YUJL27ET.js.map +0 -1
package/dist/transfer/types.d.ts
CHANGED
|
@@ -313,13 +313,13 @@ declare const CapsuleBlockSchema: z.ZodObject<{
|
|
|
313
313
|
peerProfiles: boolean;
|
|
314
314
|
}>;
|
|
315
315
|
}, "strip", z.ZodTypeAny, {
|
|
316
|
-
schemaVersion: string;
|
|
317
316
|
includes: {
|
|
318
317
|
procedural: boolean;
|
|
319
318
|
taxonomy: boolean;
|
|
320
319
|
identityAnchors: boolean;
|
|
321
320
|
peerProfiles: boolean;
|
|
322
321
|
};
|
|
322
|
+
schemaVersion: string;
|
|
323
323
|
id: string;
|
|
324
324
|
version: string;
|
|
325
325
|
description: string;
|
|
@@ -334,13 +334,13 @@ declare const CapsuleBlockSchema: z.ZodObject<{
|
|
|
334
334
|
directAnswerEnabled: boolean;
|
|
335
335
|
};
|
|
336
336
|
}, {
|
|
337
|
-
schemaVersion: string;
|
|
338
337
|
includes: {
|
|
339
338
|
procedural: boolean;
|
|
340
339
|
taxonomy: boolean;
|
|
341
340
|
identityAnchors: boolean;
|
|
342
341
|
peerProfiles: boolean;
|
|
343
342
|
};
|
|
343
|
+
schemaVersion: string;
|
|
344
344
|
id: string;
|
|
345
345
|
version: string;
|
|
346
346
|
description: string;
|
|
@@ -464,13 +464,13 @@ declare const ExportManifestV2Schema: z.ZodObject<{
|
|
|
464
464
|
peerProfiles: boolean;
|
|
465
465
|
}>;
|
|
466
466
|
}, "strip", z.ZodTypeAny, {
|
|
467
|
-
schemaVersion: string;
|
|
468
467
|
includes: {
|
|
469
468
|
procedural: boolean;
|
|
470
469
|
taxonomy: boolean;
|
|
471
470
|
identityAnchors: boolean;
|
|
472
471
|
peerProfiles: boolean;
|
|
473
472
|
};
|
|
473
|
+
schemaVersion: string;
|
|
474
474
|
id: string;
|
|
475
475
|
version: string;
|
|
476
476
|
description: string;
|
|
@@ -485,13 +485,13 @@ declare const ExportManifestV2Schema: z.ZodObject<{
|
|
|
485
485
|
directAnswerEnabled: boolean;
|
|
486
486
|
};
|
|
487
487
|
}, {
|
|
488
|
-
schemaVersion: string;
|
|
489
488
|
includes: {
|
|
490
489
|
procedural: boolean;
|
|
491
490
|
taxonomy: boolean;
|
|
492
491
|
identityAnchors: boolean;
|
|
493
492
|
peerProfiles: boolean;
|
|
494
493
|
};
|
|
494
|
+
schemaVersion: string;
|
|
495
495
|
id: string;
|
|
496
496
|
version: string;
|
|
497
497
|
description: string;
|
|
@@ -518,13 +518,13 @@ declare const ExportManifestV2Schema: z.ZodObject<{
|
|
|
518
518
|
pluginVersion: string;
|
|
519
519
|
includesTranscripts: boolean;
|
|
520
520
|
capsule: {
|
|
521
|
-
schemaVersion: string;
|
|
522
521
|
includes: {
|
|
523
522
|
procedural: boolean;
|
|
524
523
|
taxonomy: boolean;
|
|
525
524
|
identityAnchors: boolean;
|
|
526
525
|
peerProfiles: boolean;
|
|
527
526
|
};
|
|
527
|
+
schemaVersion: string;
|
|
528
528
|
id: string;
|
|
529
529
|
version: string;
|
|
530
530
|
description: string;
|
|
@@ -551,13 +551,13 @@ declare const ExportManifestV2Schema: z.ZodObject<{
|
|
|
551
551
|
pluginVersion: string;
|
|
552
552
|
includesTranscripts: boolean;
|
|
553
553
|
capsule: {
|
|
554
|
-
schemaVersion: string;
|
|
555
554
|
includes: {
|
|
556
555
|
procedural: boolean;
|
|
557
556
|
taxonomy: boolean;
|
|
558
557
|
identityAnchors: boolean;
|
|
559
558
|
peerProfiles: boolean;
|
|
560
559
|
};
|
|
560
|
+
schemaVersion: string;
|
|
561
561
|
id: string;
|
|
562
562
|
version: string;
|
|
563
563
|
description: string;
|
|
@@ -683,13 +683,13 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
683
683
|
peerProfiles: boolean;
|
|
684
684
|
}>;
|
|
685
685
|
}, "strip", z.ZodTypeAny, {
|
|
686
|
-
schemaVersion: string;
|
|
687
686
|
includes: {
|
|
688
687
|
procedural: boolean;
|
|
689
688
|
taxonomy: boolean;
|
|
690
689
|
identityAnchors: boolean;
|
|
691
690
|
peerProfiles: boolean;
|
|
692
691
|
};
|
|
692
|
+
schemaVersion: string;
|
|
693
693
|
id: string;
|
|
694
694
|
version: string;
|
|
695
695
|
description: string;
|
|
@@ -704,13 +704,13 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
704
704
|
directAnswerEnabled: boolean;
|
|
705
705
|
};
|
|
706
706
|
}, {
|
|
707
|
-
schemaVersion: string;
|
|
708
707
|
includes: {
|
|
709
708
|
procedural: boolean;
|
|
710
709
|
taxonomy: boolean;
|
|
711
710
|
identityAnchors: boolean;
|
|
712
711
|
peerProfiles: boolean;
|
|
713
712
|
};
|
|
713
|
+
schemaVersion: string;
|
|
714
714
|
id: string;
|
|
715
715
|
version: string;
|
|
716
716
|
description: string;
|
|
@@ -737,13 +737,13 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
737
737
|
pluginVersion: string;
|
|
738
738
|
includesTranscripts: boolean;
|
|
739
739
|
capsule: {
|
|
740
|
-
schemaVersion: string;
|
|
741
740
|
includes: {
|
|
742
741
|
procedural: boolean;
|
|
743
742
|
taxonomy: boolean;
|
|
744
743
|
identityAnchors: boolean;
|
|
745
744
|
peerProfiles: boolean;
|
|
746
745
|
};
|
|
746
|
+
schemaVersion: string;
|
|
747
747
|
id: string;
|
|
748
748
|
version: string;
|
|
749
749
|
description: string;
|
|
@@ -770,13 +770,13 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
770
770
|
pluginVersion: string;
|
|
771
771
|
includesTranscripts: boolean;
|
|
772
772
|
capsule: {
|
|
773
|
-
schemaVersion: string;
|
|
774
773
|
includes: {
|
|
775
774
|
procedural: boolean;
|
|
776
775
|
taxonomy: boolean;
|
|
777
776
|
identityAnchors: boolean;
|
|
778
777
|
peerProfiles: boolean;
|
|
779
778
|
};
|
|
779
|
+
schemaVersion: string;
|
|
780
780
|
id: string;
|
|
781
781
|
version: string;
|
|
782
782
|
description: string;
|
|
@@ -819,13 +819,13 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
819
819
|
pluginVersion: string;
|
|
820
820
|
includesTranscripts: boolean;
|
|
821
821
|
capsule: {
|
|
822
|
-
schemaVersion: string;
|
|
823
822
|
includes: {
|
|
824
823
|
procedural: boolean;
|
|
825
824
|
taxonomy: boolean;
|
|
826
825
|
identityAnchors: boolean;
|
|
827
826
|
peerProfiles: boolean;
|
|
828
827
|
};
|
|
828
|
+
schemaVersion: string;
|
|
829
829
|
id: string;
|
|
830
830
|
version: string;
|
|
831
831
|
description: string;
|
|
@@ -858,13 +858,13 @@ declare const ExportBundleV2Schema: z.ZodObject<{
|
|
|
858
858
|
pluginVersion: string;
|
|
859
859
|
includesTranscripts: boolean;
|
|
860
860
|
capsule: {
|
|
861
|
-
schemaVersion: string;
|
|
862
861
|
includes: {
|
|
863
862
|
procedural: boolean;
|
|
864
863
|
taxonomy: boolean;
|
|
865
864
|
identityAnchors: boolean;
|
|
866
865
|
peerProfiles: boolean;
|
|
867
866
|
};
|
|
867
|
+
schemaVersion: string;
|
|
868
868
|
id: string;
|
|
869
869
|
version: string;
|
|
870
870
|
description: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remnic/core",
|
|
3
|
-
"version": "9.3.
|
|
3
|
+
"version": "9.3.716",
|
|
4
4
|
"description": "Framework-agnostic Remnic memory engine — orchestrator, storage, extraction, search, trust zones",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -2966,7 +2966,7 @@
|
|
|
2966
2966
|
"core"
|
|
2967
2967
|
],
|
|
2968
2968
|
"peerDependencies": {
|
|
2969
|
-
"@remnic/coding-graph": "^9.3.
|
|
2969
|
+
"@remnic/coding-graph": "^9.3.716"
|
|
2970
2970
|
},
|
|
2971
2971
|
"peerDependenciesMeta": {
|
|
2972
2972
|
"@remnic/coding-graph": {
|
package/src/capabilities.test.ts
CHANGED
|
@@ -96,6 +96,34 @@ test("resolveCapabilities returns a frozen object", () => {
|
|
|
96
96
|
assert.equal(Object.isFrozen(caps), true, "CapabilitySet must be frozen");
|
|
97
97
|
});
|
|
98
98
|
|
|
99
|
+
test("resolveCapabilities is deterministic — same config always yields identical gate values (one-resolution-per-op #1523)", () => {
|
|
100
|
+
// The one-resolution-per-op contract (issue #1523): resolve ONCE at the
|
|
101
|
+
// operation entry, thread the frozen result down. This test proves the
|
|
102
|
+
// resolution is deterministic — calling it twice with the same config
|
|
103
|
+
// produces identical values for every gate, so there is never a reason to
|
|
104
|
+
// re-resolve mid-operation.
|
|
105
|
+
const config = parseConfig({
|
|
106
|
+
rerankCacheEnabled: true,
|
|
107
|
+
recallDirectAnswerEnabled: false,
|
|
108
|
+
recallMmrEnabled: true,
|
|
109
|
+
recallPlannerLlmEnabled: true,
|
|
110
|
+
graphRecallEnabled: true,
|
|
111
|
+
});
|
|
112
|
+
const first = resolveCapabilities(config);
|
|
113
|
+
const second = resolveCapabilities(config);
|
|
114
|
+
for (const field of FIELDS) {
|
|
115
|
+
assert.equal(
|
|
116
|
+
first[field],
|
|
117
|
+
second[field],
|
|
118
|
+
`field ${field} must be deterministic across resolutions`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
// Both must be frozen — a mid-operation mutation would break the contract.
|
|
122
|
+
assert.equal(Object.isFrozen(first), true);
|
|
123
|
+
assert.equal(Object.isFrozen(second), true);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
|
|
99
127
|
// ---------------------------------------------------------------------------
|
|
100
128
|
// GraphConstructionCapabilitySet — gate-parity tests (issue #1566 Cluster A).
|
|
101
129
|
//
|
|
@@ -13,7 +13,6 @@ import type { TrustZoneName } from "./trust-zones.js";
|
|
|
13
13
|
type WiringConfig = DirectAnswerWiringInput["config"];
|
|
14
14
|
|
|
15
15
|
const BASE_CONFIG: WiringConfig = {
|
|
16
|
-
recallDirectAnswerEnabled: true,
|
|
17
16
|
recallDirectAnswerTokenOverlapFloor: 0.5,
|
|
18
17
|
recallDirectAnswerImportanceFloor: 0.7,
|
|
19
18
|
recallDirectAnswerAmbiguityMargin: 0.15,
|
|
@@ -105,17 +104,24 @@ test("tryDirectAnswer disabled-path does not call any source accessor", async ()
|
|
|
105
104
|
assert.deepEqual(sources.calls.importance, []);
|
|
106
105
|
});
|
|
107
106
|
|
|
108
|
-
// ──
|
|
107
|
+
// ── One-resolution-per-op (#1523): `enabled` is the sole gate ──────────────
|
|
109
108
|
|
|
110
|
-
test("tryDirectAnswer
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
|
|
109
|
+
test("tryDirectAnswer disabled when `enabled: false` even with matching candidates (caps wins over any config-derived value)", async () => {
|
|
110
|
+
// Issue #1523 contract: the resolved capability is the sole gate.
|
|
111
|
+
// `enabled` comes from `resolveCapabilities(config).recallDirectAnswer`,
|
|
112
|
+
// resolved ONCE at the recall entry. This test proves the gate value is
|
|
113
|
+
// authoritative — when it says disabled, the function short-circuits
|
|
114
|
+
// regardless of candidate quality.
|
|
115
|
+
const sources = makeMockSources({
|
|
116
|
+
memories: [makeMemory({ tags: ["pnpm"], content: "remnic uses pnpm" })],
|
|
117
|
+
trustZones: { m1: "trusted" },
|
|
118
|
+
importance: { m1: 0.9 },
|
|
119
|
+
});
|
|
115
120
|
const result = await tryDirectAnswer({
|
|
116
|
-
query: "
|
|
121
|
+
query: "package manager remnic",
|
|
117
122
|
namespace: "default",
|
|
118
|
-
config:
|
|
123
|
+
config: BASE_CONFIG,
|
|
124
|
+
enabled: false,
|
|
119
125
|
sources,
|
|
120
126
|
});
|
|
121
127
|
assert.equal(result.eligible, false);
|
|
@@ -123,9 +129,9 @@ test("tryDirectAnswer falls back to config.recallDirectAnswerEnabled when `enabl
|
|
|
123
129
|
assert.equal(sources.calls.listCandidates, 0);
|
|
124
130
|
});
|
|
125
131
|
|
|
126
|
-
test("tryDirectAnswer
|
|
127
|
-
//
|
|
128
|
-
//
|
|
132
|
+
test("tryDirectAnswer proceeds when `enabled: true` and candidates match", async () => {
|
|
133
|
+
// Same contract, positive case: when the resolved capability says enabled,
|
|
134
|
+
// the function proceeds to materialize candidates.
|
|
129
135
|
const sources = makeMockSources({
|
|
130
136
|
memories: [makeMemory({ tags: ["pnpm"], content: "remnic uses pnpm" })],
|
|
131
137
|
trustZones: { m1: "trusted" },
|
|
@@ -134,7 +140,8 @@ test("tryDirectAnswer falls back to config.recallDirectAnswerEnabled when `enabl
|
|
|
134
140
|
const result = await tryDirectAnswer({
|
|
135
141
|
query: "package manager remnic",
|
|
136
142
|
namespace: "default",
|
|
137
|
-
config: BASE_CONFIG,
|
|
143
|
+
config: BASE_CONFIG,
|
|
144
|
+
enabled: true,
|
|
138
145
|
sources,
|
|
139
146
|
});
|
|
140
147
|
assert.notEqual(result.reason, "disabled");
|
|
@@ -14,8 +14,9 @@
|
|
|
14
14
|
*
|
|
15
15
|
* Short-circuit contract:
|
|
16
16
|
*
|
|
17
|
-
* - When the resolved gate (`input.enabled
|
|
18
|
-
* `
|
|
17
|
+
* - When the resolved gate (`input.enabled`, projected from
|
|
18
|
+
* `caps.recallDirectAnswer` at the recall-operation entry) is `false`, the
|
|
19
|
+
* function returns the
|
|
19
20
|
* eligibility verdict with reason `"disabled"` without touching any source
|
|
20
21
|
* accessor. This is the documented default.
|
|
21
22
|
* - When enabled, the wiring cheaply drops non-trusted-zone memories
|
|
@@ -74,7 +75,6 @@ export interface DirectAnswerWiringInput {
|
|
|
74
75
|
namespace: string;
|
|
75
76
|
config: Pick<
|
|
76
77
|
PluginConfig,
|
|
77
|
-
| "recallDirectAnswerEnabled"
|
|
78
78
|
| "recallDirectAnswerTokenOverlapFloor"
|
|
79
79
|
| "recallDirectAnswerImportanceFloor"
|
|
80
80
|
| "recallDirectAnswerAmbiguityMargin"
|
|
@@ -82,13 +82,12 @@ export interface DirectAnswerWiringInput {
|
|
|
82
82
|
>;
|
|
83
83
|
/**
|
|
84
84
|
* Direct-answer capability gate, resolved once at the recall-operation entry
|
|
85
|
-
* (issue #1523
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* shape keep identical behavior.
|
|
85
|
+
* via `resolveCapabilities(config).recallDirectAnswer` (issue #1523). This
|
|
86
|
+
* is the sole gate — the function no longer reads the config flag directly.
|
|
87
|
+
* Callers MUST pass the resolved capability so the whole operation agrees on
|
|
88
|
+
* a single gate value.
|
|
90
89
|
*/
|
|
91
|
-
enabled
|
|
90
|
+
enabled: boolean;
|
|
92
91
|
sources: DirectAnswerSources;
|
|
93
92
|
queryEntityRefs?: string[];
|
|
94
93
|
abortSignal?: AbortSignal;
|
|
@@ -105,10 +104,7 @@ export async function tryDirectAnswer(
|
|
|
105
104
|
const { query, namespace, config, enabled, sources, queryEntityRefs, abortSignal } = input;
|
|
106
105
|
|
|
107
106
|
const eligibilityConfig: DirectAnswerConfig = {
|
|
108
|
-
|
|
109
|
-
// flag so callers on the old input shape (config-only, no `enabled`) get
|
|
110
|
-
// identical gating (issue #1523 backward-compat).
|
|
111
|
-
enabled: enabled ?? config.recallDirectAnswerEnabled,
|
|
107
|
+
enabled,
|
|
112
108
|
tokenOverlapFloor: config.recallDirectAnswerTokenOverlapFloor,
|
|
113
109
|
importanceFloor: config.recallDirectAnswerImportanceFloor,
|
|
114
110
|
ambiguityMargin: config.recallDirectAnswerAmbiguityMargin,
|
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
|
|
@@ -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
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/taxonomy/resolver.ts","../src/direct-answer-wiring.ts"],"sourcesContent":["/**\n * Resolver decision tree for the MECE taxonomy.\n *\n * Given extracted content and its MemoryCategory, determines which\n * taxonomy category the knowledge should be filed under.\n */\n\nimport type { MemoryCategory } from \"../types.js\";\nimport type { ResolverDecision, Taxonomy, TaxonomyCategory } from \"./types.js\";\n\nconst DEFAULT_CATEGORY_ID = \"facts\";\n\n/**\n * Resolve a piece of content to a taxonomy category.\n *\n * Algorithm:\n * 1. Find all taxonomy categories whose `memoryCategories` include\n * the given `memoryCategory`.\n * 2. If exactly one match, return it with confidence 1.0.\n * 3. If multiple matches, prefer the strongest exact-token keyword\n * overlap from filing rules. Use priority only as a tie-breaker.\n * If no filing rules match, choose the explicit/default fallback\n * category instead of letting low-priority generic terms win.\n * 4. If no match, fall back to the \"facts\" category (or first\n * category if \"facts\" is absent) with low confidence.\n * 5. Always populate `alternatives` with other plausible categories.\n */\nexport function resolveCategory(\n content: string,\n memoryCategory: MemoryCategory,\n taxonomy: Taxonomy,\n): ResolverDecision {\n const contentTokens = tokenizeKeywordText(content);\n\n // Step 1: find matching categories\n const matches = taxonomy.categories.filter((cat) =>\n cat.memoryCategories.includes(memoryCategory),\n );\n\n if (matches.length === 0) {\n // No taxonomy category accepts this MemoryCategory — fall back\n const fallback =\n taxonomy.categories.find((c) => c.id === DEFAULT_CATEGORY_ID) ??\n taxonomy.categories[0];\n if (!fallback) {\n return {\n categoryId: DEFAULT_CATEGORY_ID,\n confidence: 0,\n reason: \"Taxonomy is empty; using default category\",\n alternatives: [],\n };\n }\n const alternatives = taxonomy.categories\n .filter((c) => c.id !== fallback.id)\n .map((c) => ({\n categoryId: c.id,\n reason: c.description,\n }));\n return {\n categoryId: fallback.id,\n confidence: 0.3,\n reason: `No taxonomy category maps to MemoryCategory \"${memoryCategory}\"; falling back to \"${fallback.name}\"`,\n alternatives,\n };\n }\n\n if (matches.length === 1) {\n const match = matches[0]!;\n const alternatives = taxonomy.categories\n .filter((c) => c.id !== match.id)\n .map((c) => ({\n categoryId: c.id,\n reason: c.description,\n }));\n return {\n categoryId: match.id,\n confidence: 1.0,\n reason: `Unique match: MemoryCategory \"${memoryCategory}\" maps to \"${match.name}\"`,\n alternatives,\n };\n }\n\n // Multiple matches — use filing rule keyword heuristics + priority\n const scored = matches.map((cat) => ({\n cat,\n keywordScore: computeKeywordScoreForTokens(contentTokens, cat),\n }));\n\n // Sort by keyword score descending, then priority ascending (lower wins)\n scored.sort((a, b) => {\n if (b.keywordScore !== a.keywordScore) return b.keywordScore - a.keywordScore;\n return a.cat.priority - b.cat.priority;\n });\n\n const topScored = scored[0]!;\n const best = topScored.keywordScore > 0\n ? topScored\n : {\n cat: selectFallbackCategory(matches) ?? topScored.cat,\n keywordScore: 0,\n };\n const runnerUp = scored[1];\n\n // Confidence is higher when keyword match clearly differentiates\n const confidence =\n best.keywordScore > 0 && (!runnerUp || best.keywordScore > runnerUp.keywordScore)\n ? 0.9\n : 0.7;\n\n const alternatives = taxonomy.categories\n .filter((c) => c.id !== best.cat.id)\n .map((c) => ({\n categoryId: c.id,\n reason: c.description,\n }));\n\n const reason =\n best.keywordScore > 0\n ? `Filing rules for \"${best.cat.name}\" matched content keywords (priority ${best.cat.priority})`\n : `No filing rules matched content keywords; using fallback category \"${best.cat.name}\"`;\n\n return {\n categoryId: best.cat.id,\n confidence,\n reason,\n alternatives,\n };\n}\n\n/**\n * Compute a simple keyword overlap score between content and\n * a category's filing rules + description.\n */\nfunction computeKeywordScoreForTokens(contentTokens: Set<string>, cat: TaxonomyCategory): number {\n let score = 0;\n const ruleText = [...cat.filingRules, cat.description]\n .join(\" \")\n .toLowerCase();\n\n const keywords = tokenizeKeywordText(ruleText);\n\n for (const kw of keywords) {\n if (contentTokens.has(kw)) {\n score += 1;\n }\n }\n return score;\n}\n\nfunction tokenizeKeywordText(value: string): Set<string> {\n return new Set(\n value\n .toLowerCase()\n .split(/[^a-z0-9]+/)\n .map((word) => word.trim())\n .filter((word) => word.length >= 3 && !TAXONOMY_KEYWORD_STOPWORDS.has(word)),\n );\n}\n\nfunction selectFallbackCategory(\n categories: readonly TaxonomyCategory[],\n): TaxonomyCategory | undefined {\n return categories.find((cat) => cat.id === DEFAULT_CATEGORY_ID) ??\n categories.find((cat) => {\n const text = `${cat.id} ${cat.name} ${cat.description} ${cat.filingRules.join(\" \")}`.toLowerCase();\n return /\\b(general|fallback)\\b/.test(text);\n });\n}\n\nconst TAXONOMY_KEYWORD_STOPWORDS = new Set([\n \"about\",\n \"all\",\n \"and\",\n \"are\",\n \"for\",\n \"from\",\n \"has\",\n \"into\",\n \"not\",\n \"the\",\n \"this\",\n \"was\",\n \"with\",\n]);\n","/**\n * Direct-answer wiring (issue #518 slice 3).\n *\n * Binds the pure eligibility decision (`direct-answer.ts`) to the data\n * sources needed to build candidates: storage, trust-zones, taxonomy,\n * and importance scoring. Kept as a separate module so that:\n *\n * - The eligibility layer stays pure and unit-testable without stores.\n * - Each caller injects its own source accessors. The orchestrator\n * binding is a follow-on slice; tests here use mock sources.\n * - The wiring is safe to ship alone — nothing calls `tryDirectAnswer`\n * yet, so enabling this module's presence does not change recall\n * behavior. The next slice adds exactly one call site before QMD.\n *\n * Short-circuit contract:\n *\n * - When the resolved gate (`input.enabled` if supplied, else\n * `config.recallDirectAnswerEnabled`) is `false`, the function returns the\n * eligibility verdict with reason `\"disabled\"` without touching any source\n * accessor. This is the documented default.\n * - When enabled, the wiring cheaply drops non-trusted-zone memories\n * and ineligible taxonomy buckets before computing importance, so\n * the eligibility module sees a pre-filtered candidate set. The\n * eligibility module still performs the same checks itself — this\n * module is purely an I/O and prefiltering layer.\n */\n\nimport type { MemoryFile, PluginConfig } from \"./types.js\";\nimport type { TrustZoneName } from \"./trust-zones.js\";\nimport type { Taxonomy } from \"./taxonomy/types.js\";\nimport { resolveCategory } from \"./taxonomy/resolver.js\";\nimport { normalizeRecallTokens } from \"./recall-tokenization.js\";\nimport { throwIfAborted } from \"./abort-error.js\";\nimport {\n isDirectAnswerEligible,\n type DirectAnswerCandidate,\n type DirectAnswerConfig,\n type DirectAnswerResult,\n} from \"./direct-answer.js\";\n\n/**\n * Caller-provided accessors for candidate sourcing. Decouples the\n * wiring from any specific storage / trust-zone / importance backend.\n */\nexport interface DirectAnswerSources {\n /**\n * List memories eligible to be considered for direct-answer.\n * Callers are expected to return only active, non-superseded memories\n * in the requested namespace; the wiring will cheaply re-filter on\n * trust zone and taxonomy bucket and hand the rest to the eligibility\n * module, which applies the full gate ladder.\n */\n listCandidateMemories(options: {\n namespace: string;\n abortSignal?: AbortSignal;\n }): Promise<MemoryFile[]>;\n /**\n * Resolve the trust-zone record for a memory. Returns `null` when\n * the memory has no trust-zone record (treated as not trusted).\n */\n trustZoneFor(memoryId: string): Promise<TrustZoneName | null>;\n /**\n * Resolve a calibrated importance score in [0, 1] for a memory.\n */\n importanceFor(memory: MemoryFile): number;\n /**\n * Taxonomy used to classify memories into direct-answer buckets.\n */\n taxonomy: Taxonomy;\n}\n\nexport interface DirectAnswerWiringInput {\n query: string;\n namespace: string;\n config: Pick<\n PluginConfig,\n | \"recallDirectAnswerEnabled\"\n | \"recallDirectAnswerTokenOverlapFloor\"\n | \"recallDirectAnswerImportanceFloor\"\n | \"recallDirectAnswerAmbiguityMargin\"\n | \"recallDirectAnswerEligibleTaxonomyBuckets\"\n >;\n /**\n * Direct-answer capability gate, resolved once at the recall-operation entry\n * (issue #1523: `caps.recallDirectAnswer`). OPTIONAL and additive: when the\n * caller supplies it, this module and the orchestrator agree on a single\n * resolved gate value for the whole operation. When omitted, we fall back to\n * `config.recallDirectAnswerEnabled` so existing callers on the old input\n * shape keep identical behavior.\n */\n enabled?: boolean;\n sources: DirectAnswerSources;\n queryEntityRefs?: string[];\n abortSignal?: AbortSignal;\n}\n\n/**\n * Attempt direct-answer resolution. Returns the eligibility verdict\n * produced by `isDirectAnswerEligible` with candidates materialized\n * from the caller-supplied sources.\n */\nexport async function tryDirectAnswer(\n input: DirectAnswerWiringInput,\n): Promise<DirectAnswerResult> {\n const { query, namespace, config, enabled, sources, queryEntityRefs, abortSignal } = input;\n\n const eligibilityConfig: DirectAnswerConfig = {\n // Prefer the resolved capability when supplied; fall back to the config\n // flag so callers on the old input shape (config-only, no `enabled`) get\n // identical gating (issue #1523 backward-compat).\n enabled: enabled ?? config.recallDirectAnswerEnabled,\n tokenOverlapFloor: config.recallDirectAnswerTokenOverlapFloor,\n importanceFloor: config.recallDirectAnswerImportanceFloor,\n ambiguityMargin: config.recallDirectAnswerAmbiguityMargin,\n eligibleTaxonomyBuckets: config.recallDirectAnswerEligibleTaxonomyBuckets,\n };\n\n // Short-circuit disabled case before touching any I/O.\n if (!eligibilityConfig.enabled) {\n return isDirectAnswerEligible({\n query,\n candidates: [],\n config: eligibilityConfig,\n queryEntityRefs,\n });\n }\n\n // Short-circuit empty-query case before any I/O. isDirectAnswerEligible\n // deterministically returns reason \"empty-query\" when the query\n // normalizes to zero searchable tokens; there's no point materializing\n // candidates just to reach the same verdict, and doing so would\n // surface avoidable upstream errors for requests that should exit\n // immediately.\n if (normalizeRecallTokens(query).length === 0) {\n return isDirectAnswerEligible({\n query,\n candidates: [],\n config: eligibilityConfig,\n queryEntityRefs,\n });\n }\n\n throwIfAborted(abortSignal, \"direct-answer wiring aborted\");\n const memories = await sources.listCandidateMemories({ namespace, abortSignal });\n throwIfAborted(abortSignal, \"direct-answer wiring aborted\");\n const candidates: DirectAnswerCandidate[] = [];\n\n for (const memory of memories) {\n // Throw rather than returning a partial verdict — a mid-loop abort\n // that left competing candidates unprocessed could otherwise surface\n // a spurious \"eligible\" result that the ambiguity gate never got a\n // chance to reject. The check repeats after every await so an\n // abort that lands during the in-flight I/O on the final memory\n // (after which no further iteration would exist) still stops us.\n throwIfAborted(abortSignal, \"direct-answer wiring aborted\");\n\n const trustZone = await sources.trustZoneFor(memory.frontmatter.id);\n throwIfAborted(abortSignal, \"direct-answer wiring aborted\");\n\n // Cheap pre-filter: non-trusted memories can't qualify, so skip\n // taxonomy and importance resolution for them.\n if (trustZone !== \"trusted\") continue;\n\n const decision = resolveCategory(\n memory.content,\n memory.frontmatter.category,\n sources.taxonomy,\n );\n const taxonomyBucket = decision.categoryId;\n if (!eligibilityConfig.eligibleTaxonomyBuckets.includes(taxonomyBucket)) continue;\n\n const importanceScore = sources.importanceFor(memory);\n\n candidates.push({\n memory,\n trustZone,\n taxonomyBucket,\n importanceScore,\n });\n }\n\n // Final check — if abort landed during the trust-zone await for the\n // last memory, the loop condition no longer fires. Guard before we\n // hand candidates to the eligibility gate.\n throwIfAborted(abortSignal, \"direct-answer wiring aborted\");\n\n return isDirectAnswerEligible({\n query,\n candidates,\n config: eligibilityConfig,\n queryEntityRefs,\n });\n}\n"],"mappings":"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","names":["alternatives"]}
|