network-ai 5.14.0 → 5.15.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.
@@ -0,0 +1,332 @@
1
+ "use strict";
2
+ /**
3
+ * Context Composer — token-budgeted, relevance-ranked context assembly.
4
+ *
5
+ * Agents have large context windows but a much smaller *effective reasoning*
6
+ * window: irrelevant, stale, or noisy context degrades output quality long
7
+ * before the hard token limit ("context rot"). This module assembles the
8
+ * context block for an LLM call from candidate sources (blackboard entries,
9
+ * project context, memory recall) under a **hard token budget**, ranked by:
10
+ *
11
+ * score = w.relevance × relevance + w.recency × recency + w.affinity × affinity
12
+ *
13
+ * - **relevance** — semantic similarity to the task via a pluggable
14
+ * {@link SemanticRanker} (BYOE — e.g. wrap `SemanticMemory`), with a
15
+ * deterministic lexical-overlap fallback when no ranker is supplied;
16
+ * - **recency** — exponential half-life decay on the entry timestamp
17
+ * (the same math `EpisodicMemory` uses);
18
+ * - **affinity** — scope-tag match on the key (same substring semantics as
19
+ * `ContextThrottler`).
20
+ *
21
+ * Pinned sources (task-critical instructions, Layer-3 project context) are
22
+ * always included first. Assembly is position-aware by default: the
23
+ * strongest items are placed at the start *and end* of the block, weakest in
24
+ * the middle — mitigating "lost in the middle" attention decay.
25
+ *
26
+ * The result carries full observability metadata: what was included,
27
+ * what was excluded and why, token usage, and budget utilization.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * const composer = new ContextComposer();
32
+ * const sources = ContextComposer.fromSnapshot(blackboard.getScopedSnapshot('analyst'));
33
+ * const pack = await composer.compose(sources, {
34
+ * task: 'Summarize Q3 revenue anomalies',
35
+ * budgetTokens: 2000,
36
+ * scopeTags: ['analytics', 'task'],
37
+ * });
38
+ * llmPrompt = `${instructions}\n\n${pack.text}`;
39
+ * ```
40
+ *
41
+ * @module ContextComposer
42
+ * @version 1.0.0
43
+ */
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.ContextComposer = void 0;
46
+ exports.estimateTokens = estimateTokens;
47
+ exports.createSemanticMemoryRanker = createSemanticMemoryRanker;
48
+ // ============================================================================
49
+ // TOKEN ESTIMATION
50
+ // ============================================================================
51
+ /**
52
+ * Estimate the token count of a text without a tokenizer dependency.
53
+ *
54
+ * Uses the ~4-characters-per-token heuristic blended with a word count
55
+ * (English prose averages ~0.75 tokens/word; code and JSON run denser).
56
+ * Accurate to roughly ±15% across prose/code/JSON — sufficient for budget
57
+ * enforcement, not for billing.
58
+ */
59
+ function estimateTokens(text) {
60
+ if (!text)
61
+ return 0;
62
+ const byChars = text.length / 4;
63
+ const words = text.split(/\s+/).filter(Boolean).length;
64
+ const byWords = words * 1.33;
65
+ return Math.max(1, Math.ceil((byChars + byWords) / 2));
66
+ }
67
+ // ============================================================================
68
+ // INTERNAL HELPERS
69
+ // ============================================================================
70
+ /** Tokenize text into a lowercase word set for lexical overlap scoring. */
71
+ function wordSet(text) {
72
+ return new Set(text
73
+ .toLowerCase()
74
+ .split(/[^a-z0-9_]+/)
75
+ .filter((w) => w.length > 2));
76
+ }
77
+ /**
78
+ * Deterministic lexical relevance: fraction of task words present in the
79
+ * item (key + text). Zero-dependency fallback when no semantic ranker is
80
+ * configured.
81
+ */
82
+ function lexicalRelevance(taskWords, item) {
83
+ if (taskWords.size === 0)
84
+ return 0;
85
+ const itemWords = wordSet(`${item.key} ${item.text}`);
86
+ if (itemWords.size === 0)
87
+ return 0;
88
+ let hits = 0;
89
+ for (const w of taskWords) {
90
+ if (itemWords.has(w))
91
+ hits++;
92
+ }
93
+ return hits / taskWords.size;
94
+ }
95
+ /** Exponential half-life recency decay (1 = now, → 0 with age). */
96
+ function recencyScore(timestamp, halfLifeMs, now) {
97
+ if (!timestamp)
98
+ return 0.5; // unknown age — neutral
99
+ const t = Date.parse(timestamp);
100
+ if (Number.isNaN(t))
101
+ return 0.5;
102
+ const elapsed = Math.max(0, now - t);
103
+ return Math.pow(0.5, elapsed / halfLifeMs);
104
+ }
105
+ /** Scope-tag affinity — ContextThrottler substring semantics on the key. */
106
+ function affinityScore(key, scopeTags) {
107
+ if (!scopeTags || scopeTags.length === 0)
108
+ return 0.5; // no scoping — neutral
109
+ const lowerKey = key.toLowerCase();
110
+ return scopeTags.some((tag) => lowerKey.includes(tag.toLowerCase())) ? 1 : 0;
111
+ }
112
+ /** True when the entry's TTL has already elapsed relative to its timestamp. */
113
+ function isStale(source, now) {
114
+ if (source.ttl === null || source.ttl === undefined)
115
+ return false;
116
+ if (!source.timestamp)
117
+ return false;
118
+ const t = Date.parse(source.timestamp);
119
+ if (Number.isNaN(t))
120
+ return false;
121
+ return now - t > source.ttl * 1000;
122
+ }
123
+ /** Render one item as a labelled context block. */
124
+ function renderBlock(item) {
125
+ const origin = item.sourceAgent ? ` (from ${item.sourceAgent})` : '';
126
+ return `### ${item.key}${origin}\n${item.text}`;
127
+ }
128
+ /**
129
+ * Serpentine layout: ranked items `[1,2,3,4,5]` become `[1,3,5,4,2]` —
130
+ * strongest first, second-strongest last, weakest in the middle.
131
+ */
132
+ function positionAwareLayout(ranked) {
133
+ const head = [];
134
+ const tail = [];
135
+ ranked.forEach((item, i) => {
136
+ if (i % 2 === 0)
137
+ head.push(item);
138
+ else
139
+ tail.unshift(item);
140
+ });
141
+ return [...head, ...tail];
142
+ }
143
+ /**
144
+ * Adapt a `SemanticMemory` instance (or anything with a compatible
145
+ * `search()`) into a {@link SemanticRanker}. Items the memory does not know
146
+ * about simply fall back to lexical scoring.
147
+ */
148
+ function createSemanticMemoryRanker(memory) {
149
+ return async (query, items) => {
150
+ const results = await memory.search(query, items.length, 0);
151
+ const scores = new Map();
152
+ for (const r of results) {
153
+ // Cosine similarity may be negative — clamp to 0–1.
154
+ scores.set(r.key, Math.max(0, Math.min(1, r.score)));
155
+ }
156
+ return scores;
157
+ };
158
+ }
159
+ // ============================================================================
160
+ // CONTEXT COMPOSER
161
+ // ============================================================================
162
+ /**
163
+ * Assembles token-budgeted, relevance-ranked context packs from candidate
164
+ * sources. See the module docs for the ranking model.
165
+ */
166
+ class ContextComposer {
167
+ halfLifeMs;
168
+ weights;
169
+ ranker;
170
+ constructor(options = {}) {
171
+ this.halfLifeMs = options.halfLifeMs ?? 1_800_000;
172
+ if (this.halfLifeMs <= 0) {
173
+ throw new Error('ContextComposer: halfLifeMs must be > 0');
174
+ }
175
+ const w = { relevance: 0.5, recency: 0.3, affinity: 0.2, ...(options.weights ?? {}) };
176
+ const sum = w.relevance + w.recency + w.affinity;
177
+ if (sum <= 0) {
178
+ throw new Error('ContextComposer: score weights must sum to a positive number');
179
+ }
180
+ this.weights = {
181
+ relevance: w.relevance / sum,
182
+ recency: w.recency / sum,
183
+ affinity: w.affinity / sum,
184
+ };
185
+ this.ranker = options.ranker;
186
+ }
187
+ /**
188
+ * Convert a blackboard snapshot (`getSnapshot()` / `getScopedSnapshot()`
189
+ * shape) into {@link ContextSource} candidates. Values are rendered as
190
+ * strings (JSON for objects) and truncated to `maxValueChars`.
191
+ */
192
+ static fromSnapshot(snapshot, options = {}) {
193
+ const maxChars = options.maxValueChars ?? 4000;
194
+ const sources = [];
195
+ for (const [key, raw] of Object.entries(snapshot ?? {})) {
196
+ if (raw === null || raw === undefined)
197
+ continue;
198
+ let text;
199
+ let sourceAgent;
200
+ let timestamp;
201
+ let ttl;
202
+ if (typeof raw === 'object' && ('value' in raw)) {
203
+ const entry = raw;
204
+ const v = entry.value;
205
+ text = typeof v === 'string' ? v : JSON.stringify(v);
206
+ sourceAgent = entry.sourceAgent ?? entry.source_agent;
207
+ timestamp = entry.timestamp;
208
+ ttl = entry.ttl;
209
+ }
210
+ else {
211
+ text = typeof raw === 'string' ? raw : JSON.stringify(raw);
212
+ }
213
+ if (text === undefined || text === null)
214
+ continue;
215
+ sources.push({
216
+ key,
217
+ text: text.length > maxChars ? `${text.slice(0, maxChars)}…[truncated]` : text,
218
+ ...(sourceAgent !== undefined ? { sourceAgent } : {}),
219
+ ...(timestamp !== undefined ? { timestamp } : {}),
220
+ ...(ttl !== undefined ? { ttl } : {}),
221
+ });
222
+ }
223
+ return sources;
224
+ }
225
+ /**
226
+ * Rank the candidate sources against the task and assemble the largest
227
+ * high-signal context block that fits the token budget.
228
+ */
229
+ async compose(sources, options) {
230
+ if (!options || typeof options.task !== 'string' || options.task.trim() === '') {
231
+ throw new Error('ContextComposer.compose: options.task must be a non-empty string');
232
+ }
233
+ if (typeof options.budgetTokens !== 'number' || options.budgetTokens <= 0) {
234
+ throw new Error('ContextComposer.compose: options.budgetTokens must be > 0');
235
+ }
236
+ const now = Date.now();
237
+ const halfLifeMs = options.halfLifeMs ?? this.halfLifeMs;
238
+ const minScore = options.minScore ?? 0.05;
239
+ const maxItems = options.maxItems ?? 0;
240
+ const w = (() => {
241
+ if (!options.weights)
242
+ return this.weights;
243
+ const merged = { ...this.weights, ...options.weights };
244
+ const sum = merged.relevance + merged.recency + merged.affinity;
245
+ return sum > 0
246
+ ? { relevance: merged.relevance / sum, recency: merged.recency / sum, affinity: merged.affinity / sum }
247
+ : this.weights;
248
+ })();
249
+ const excluded = [];
250
+ const pinned = [];
251
+ const candidates = [];
252
+ for (const source of sources ?? []) {
253
+ if (!source || typeof source.key !== 'string' || source.key === '')
254
+ continue;
255
+ if (!source.text || source.text.trim() === '') {
256
+ excluded.push({ key: source.key ?? '(unknown)', reason: 'empty' });
257
+ continue;
258
+ }
259
+ if (isStale(source, now)) {
260
+ excluded.push({ key: source.key, reason: 'stale' });
261
+ continue;
262
+ }
263
+ (source.pinned ? pinned : candidates).push(source);
264
+ }
265
+ // ── Relevance scores (semantic ranker with lexical fallback) ────────────
266
+ const taskWords = wordSet(options.task);
267
+ let semanticScores = new Map();
268
+ const ranker = options.ranker ?? this.ranker;
269
+ if (ranker && candidates.length > 0) {
270
+ try {
271
+ semanticScores = await ranker(options.task, candidates.map((c) => ({ key: c.key, text: c.text })));
272
+ }
273
+ catch {
274
+ semanticScores = new Map(); // ranker failure → lexical fallback for all
275
+ }
276
+ }
277
+ // ── Score every candidate ────────────────────────────────────────────────
278
+ const ranked = candidates.map((c) => {
279
+ const relevance = semanticScores.get(c.key) ?? lexicalRelevance(taskWords, c);
280
+ const recency = recencyScore(c.timestamp, halfLifeMs, now);
281
+ const affinity = affinityScore(c.key, options.scopeTags);
282
+ const score = w.relevance * relevance + w.recency * recency + w.affinity * affinity;
283
+ return { ...c, relevance, recency, affinity, score, tokens: estimateTokens(renderBlock(c)) };
284
+ });
285
+ ranked.sort((a, b) => b.score - a.score);
286
+ // ── Budget-enforced selection: pinned first, then by score ──────────────
287
+ const included = [];
288
+ let usedTokens = 0;
289
+ for (const p of pinned) {
290
+ const tokens = estimateTokens(renderBlock(p));
291
+ if (usedTokens + tokens > options.budgetTokens) {
292
+ excluded.push({ key: p.key, reason: 'budget' });
293
+ continue;
294
+ }
295
+ usedTokens += tokens;
296
+ included.push({ ...p, relevance: 1, recency: 1, affinity: 1, score: 1, tokens });
297
+ }
298
+ for (const item of ranked) {
299
+ if (item.score < minScore) {
300
+ excluded.push({ key: item.key, reason: 'score', score: item.score });
301
+ continue;
302
+ }
303
+ if (maxItems > 0 && included.length >= maxItems) {
304
+ excluded.push({ key: item.key, reason: 'budget', score: item.score });
305
+ continue;
306
+ }
307
+ if (usedTokens + item.tokens > options.budgetTokens) {
308
+ excluded.push({ key: item.key, reason: 'budget', score: item.score });
309
+ continue;
310
+ }
311
+ usedTokens += item.tokens;
312
+ included.push(item);
313
+ }
314
+ // ── Assembly (pinned lead; ranked items in position-aware layout) ────────
315
+ const pinnedBlocks = included.filter((i) => i.pinned);
316
+ const rankedBlocks = included.filter((i) => !i.pinned);
317
+ const layout = (options.positionAware ?? true)
318
+ ? positionAwareLayout(rankedBlocks)
319
+ : rankedBlocks;
320
+ const text = [...pinnedBlocks, ...layout].map((i) => renderBlock(i)).join('\n\n');
321
+ return {
322
+ text,
323
+ included,
324
+ excluded,
325
+ budgetTokens: options.budgetTokens,
326
+ usedTokens,
327
+ utilization: Math.min(1, usedTokens / options.budgetTokens),
328
+ };
329
+ }
330
+ }
331
+ exports.ContextComposer = ContextComposer;
332
+ //# sourceMappingURL=context-composer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-composer.js","sourceRoot":"","sources":["../../../lib/context-composer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,266 @@
1
+ "use strict";
2
+ /**
3
+ * MCP Context Tools — signal-over-noise retrieval for MCP agents.
4
+ *
5
+ * Two tools that let any MCP client (Claude Code, Codex, Gemini CLI, Cursor)
6
+ * pull *curated* shared state instead of dumping the whole blackboard into
7
+ * its own context window:
8
+ *
9
+ * - `context_pack` — "give me everything relevant to task X in
10
+ * ≤ N tokens": a token-budgeted, relevance-ranked, position-aware
11
+ * context brief assembled by {@link ContextComposer} from the agent's
12
+ * scoped blackboard snapshot.
13
+ * - `blackboard_search` — ranked top-K search over blackboard entries.
14
+ * Uses a semantic ranker when one is wired (BYOE `SemanticMemory`),
15
+ * falling back to deterministic lexical overlap scoring otherwise —
16
+ * always works out of the box.
17
+ *
18
+ * Implements the same `McpToolProvider` contract as the other tool modules,
19
+ * so it registers directly on `McpCombinedBridge`.
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * const contextTools = new ContextMcpTools({ blackboard });
24
+ * combined.register(contextTools);
25
+ * // MCP client: context_pack { task: "fix the parser bug", budget_tokens: 1500, agent_id: "claude-code" }
26
+ * ```
27
+ *
28
+ * @module mcp-tools-context
29
+ * @version 1.0.0
30
+ */
31
+ Object.defineProperty(exports, "__esModule", { value: true });
32
+ exports.ContextMcpTools = exports.CONTEXT_TOOL_DEFINITIONS = void 0;
33
+ const context_composer_1 = require("./context-composer");
34
+ // ============================================================================
35
+ // TOOL DEFINITIONS
36
+ // ============================================================================
37
+ /** MCP tool definitions for the two context tools. */
38
+ exports.CONTEXT_TOOL_DEFINITIONS = [
39
+ {
40
+ name: 'context_pack',
41
+ description: 'Assemble a token-budgeted, relevance-ranked context brief from the shared blackboard for a given task. ' +
42
+ 'Use this INSTEAD of blackboard_list + many blackboard_read calls: it returns only the entries most relevant ' +
43
+ 'to your task, ranked by semantic/lexical relevance, recency (half-life decay), and namespace affinity, ' +
44
+ 'assembled position-aware (strongest items first and last) under a hard token budget. ' +
45
+ 'Read-only — never modifies the blackboard. Returns {ok:true, text, used_tokens, budget_tokens, utilization, ' +
46
+ 'included:[{key,score,tokens}], excluded:[{key,reason}]}. The `text` field is ready to use as working context. ' +
47
+ 'Entries past their TTL are excluded automatically.',
48
+ inputSchema: {
49
+ type: 'object',
50
+ properties: {
51
+ task: {
52
+ type: 'string',
53
+ description: 'The task or question driving relevance ranking (e.g. "diagnose the failing payment webhook")',
54
+ },
55
+ agent_id: {
56
+ type: 'string',
57
+ description: 'The agent requesting the pack (used for scoped access checks and audit)',
58
+ },
59
+ budget_tokens: {
60
+ type: 'number',
61
+ description: 'Hard token budget for the returned context text (default 2000)',
62
+ },
63
+ scope_tags: {
64
+ type: 'string',
65
+ description: 'Optional comma-separated scope tags for namespace affinity (e.g. "task,analytics")',
66
+ },
67
+ max_items: {
68
+ type: 'number',
69
+ description: 'Optional hard cap on the number of included entries (0 = unlimited)',
70
+ },
71
+ },
72
+ required: ['task', 'agent_id'],
73
+ },
74
+ },
75
+ {
76
+ name: 'blackboard_search',
77
+ description: 'Search the shared blackboard for entries relevant to a query and return the top-K ranked matches. ' +
78
+ 'Use this instead of blackboard_list when you need *relevant* keys rather than *all* keys — it keeps noise ' +
79
+ 'out of your context window. Read-only. Ranks semantically when the server has an embedding provider wired, ' +
80
+ 'otherwise by deterministic lexical overlap (response includes which mode was used). ' +
81
+ 'Returns {ok:true, mode, results:[{key, score, snippet, sourceAgent}], count}. ' +
82
+ 'Follow up with blackboard_read for the full value of a specific key.',
83
+ inputSchema: {
84
+ type: 'object',
85
+ properties: {
86
+ query: {
87
+ type: 'string',
88
+ description: 'Natural-language search query (e.g. "budget decisions for Q3")',
89
+ },
90
+ agent_id: {
91
+ type: 'string',
92
+ description: 'The agent performing the search (used for scoped access checks)',
93
+ },
94
+ top_k: {
95
+ type: 'number',
96
+ description: 'Maximum number of results to return (default 5, max 50)',
97
+ },
98
+ min_score: {
99
+ type: 'number',
100
+ description: 'Minimum relevance score 0-1 (default 0)',
101
+ },
102
+ },
103
+ required: ['query', 'agent_id'],
104
+ },
105
+ },
106
+ ];
107
+ // ============================================================================
108
+ // CONTEXT MCP TOOLS
109
+ // ============================================================================
110
+ /**
111
+ * `McpToolProvider` exposing `context_pack` and `blackboard_search`.
112
+ * Register on a `McpCombinedBridge` alongside the other tool providers.
113
+ */
114
+ class ContextMcpTools {
115
+ blackboard;
116
+ composer;
117
+ ranker;
118
+ semantic;
119
+ defaultBudgetTokens;
120
+ maxBudgetTokens;
121
+ constructor(options) {
122
+ if (!options || !options.blackboard) {
123
+ throw new Error('ContextMcpTools: options.blackboard is required');
124
+ }
125
+ this.blackboard = options.blackboard;
126
+ this.semantic = Boolean(options.memory);
127
+ this.ranker = options.memory ? (0, context_composer_1.createSemanticMemoryRanker)(options.memory) : undefined;
128
+ this.composer = options.composer
129
+ ?? new context_composer_1.ContextComposer(this.ranker ? { ranker: this.ranker } : {});
130
+ this.defaultBudgetTokens = options.defaultBudgetTokens ?? 2000;
131
+ this.maxBudgetTokens = options.maxBudgetTokens ?? 32_000;
132
+ if (this.defaultBudgetTokens <= 0 || this.maxBudgetTokens <= 0) {
133
+ throw new Error('ContextMcpTools: token budgets must be > 0');
134
+ }
135
+ }
136
+ /** Returns the MCP tool definitions provided by this module. */
137
+ getDefinitions() {
138
+ return exports.CONTEXT_TOOL_DEFINITIONS;
139
+ }
140
+ /**
141
+ * Dispatch a tool call by name. All errors are caught and returned as
142
+ * `{ ok: false, error }`.
143
+ */
144
+ async call(toolName, args) {
145
+ try {
146
+ switch (toolName) {
147
+ case 'context_pack': return await this._pack(args);
148
+ case 'blackboard_search': return await this._search(args);
149
+ default:
150
+ return { ok: false, tool: toolName, error: `Unknown tool: "${toolName}"` };
151
+ }
152
+ }
153
+ catch (err) {
154
+ return {
155
+ ok: false,
156
+ tool: toolName,
157
+ error: err instanceof Error ? err.message : String(err),
158
+ };
159
+ }
160
+ }
161
+ // --------------------------------------------------------------------------
162
+ // Tool implementations
163
+ // --------------------------------------------------------------------------
164
+ async _pack(args) {
165
+ const task = this._requireString(args, 'task');
166
+ const agentId = this._requireString(args, 'agent_id');
167
+ const budget = this._clampNumber(args['budget_tokens'], this.defaultBudgetTokens, 1, this.maxBudgetTokens);
168
+ const maxItems = this._clampNumber(args['max_items'], 0, 0, 10_000);
169
+ const scopeTags = typeof args['scope_tags'] === 'string' && args['scope_tags'].trim() !== ''
170
+ ? args['scope_tags'].split(',').map((t) => t.trim()).filter(Boolean)
171
+ : undefined;
172
+ const sources = context_composer_1.ContextComposer.fromSnapshot(this._snapshot(agentId));
173
+ const pack = await this.composer.compose(sources, {
174
+ task,
175
+ budgetTokens: budget,
176
+ ...(scopeTags ? { scopeTags } : {}),
177
+ ...(maxItems > 0 ? { maxItems } : {}),
178
+ });
179
+ return {
180
+ ok: true,
181
+ tool: 'context_pack',
182
+ data: {
183
+ text: pack.text,
184
+ used_tokens: pack.usedTokens,
185
+ budget_tokens: pack.budgetTokens,
186
+ utilization: Number(pack.utilization.toFixed(3)),
187
+ mode: this.semantic ? 'semantic' : 'lexical',
188
+ included: pack.included.map((i) => ({
189
+ key: i.key,
190
+ score: Number(i.score.toFixed(3)),
191
+ tokens: i.tokens,
192
+ })),
193
+ excluded: pack.excluded.map((e) => ({
194
+ key: e.key,
195
+ reason: e.reason,
196
+ ...(e.score !== undefined ? { score: Number(e.score.toFixed(3)) } : {}),
197
+ })),
198
+ },
199
+ };
200
+ }
201
+ async _search(args) {
202
+ const query = this._requireString(args, 'query');
203
+ const agentId = this._requireString(args, 'agent_id');
204
+ const topK = this._clampNumber(args['top_k'], 5, 1, 50);
205
+ const minScore = this._clampNumber(args['min_score'], 0, 0, 1);
206
+ const sources = context_composer_1.ContextComposer.fromSnapshot(this._snapshot(agentId));
207
+ // Rank purely by relevance: reuse the composer with relevance-only
208
+ // weights and an unbounded budget, then take the top-K.
209
+ const ranked = await this.composer.compose(sources, {
210
+ task: query,
211
+ budgetTokens: this.maxBudgetTokens,
212
+ weights: { relevance: 1, recency: 0, affinity: 0 },
213
+ minScore: Math.max(minScore, 0.000_001), // drop zero-relevance items
214
+ positionAware: false,
215
+ });
216
+ const results = ranked.included
217
+ .slice(0, topK)
218
+ .map((i) => ({
219
+ key: i.key,
220
+ score: Number(i.relevance.toFixed(3)),
221
+ snippet: i.text.length > 240 ? `${i.text.slice(0, 240)}…` : i.text,
222
+ ...(i.sourceAgent ? { sourceAgent: i.sourceAgent } : {}),
223
+ }));
224
+ return {
225
+ ok: true,
226
+ tool: 'blackboard_search',
227
+ data: {
228
+ mode: this.semantic ? 'semantic' : 'lexical',
229
+ results,
230
+ count: results.length,
231
+ query_tokens: (0, context_composer_1.estimateTokens)(query),
232
+ },
233
+ };
234
+ }
235
+ // --------------------------------------------------------------------------
236
+ // Helpers
237
+ // --------------------------------------------------------------------------
238
+ _snapshot(agentId) {
239
+ if (this.blackboard.getScopedSnapshot) {
240
+ return this.blackboard.getScopedSnapshot(agentId);
241
+ }
242
+ return this.blackboard.getSnapshot();
243
+ }
244
+ _requireString(args, field) {
245
+ const val = args[field];
246
+ if (typeof val !== 'string' || val.trim() === '') {
247
+ throw new Error(`Missing required field "${field}" (must be a non-empty string)`);
248
+ }
249
+ return val;
250
+ }
251
+ /** Accept number or numeric string; clamp into [min, max]; fall back to a default. */
252
+ _clampNumber(raw, fallback, min, max) {
253
+ let n;
254
+ if (typeof raw === 'number')
255
+ n = raw;
256
+ else if (typeof raw === 'string' && raw.trim() !== '')
257
+ n = Number(raw);
258
+ else
259
+ return fallback;
260
+ if (!Number.isFinite(n))
261
+ return fallback;
262
+ return Math.min(max, Math.max(min, Math.floor(n)));
263
+ }
264
+ }
265
+ exports.ContextMcpTools = ContextMcpTools;
266
+ //# sourceMappingURL=mcp-tools-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-tools-context.js","sourceRoot":"","sources":["../../../lib/mcp-tools-context.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -218,6 +218,10 @@ export { createElicitationApprovalCallback, StdioElicitationChannel } from './li
218
218
  export type { ElicitationCreateParams, ElicitationResult, ElicitationRequestSender, ElicitationRequestedSchema, ElicitationSchemaProperty, ElicitationApprovalOptions } from './lib/mcp-elicitation';
219
219
  export { A2AServer } from './lib/a2a-server';
220
220
  export type { A2AServerOptions, A2AServerTask, A2AServerTaskState, A2ATaskExecutor } from './lib/a2a-server';
221
+ export { ContextComposer, estimateTokens, createSemanticMemoryRanker } from './lib/context-composer';
222
+ export type { ContextSource, RankedContextItem, ComposedContext, ComposeOptions, ContextComposerOptions, ScoreWeights, SemanticRanker, SemanticSearchLike, ExclusionReason, ExcludedItem } from './lib/context-composer';
223
+ export { ContextMcpTools, CONTEXT_TOOL_DEFINITIONS } from './lib/mcp-tools-context';
224
+ export type { ContextMcpToolsOptions } from './lib/mcp-tools-context';
221
225
  export { createAdapterTestSuite } from './lib/adapter-test-harness';
222
226
  export type { AdapterTestSuiteConfig, AdapterTestResult, AssertFn, SectionFn } from './lib/adapter-test-harness';
223
227
  export { SwarmTransportServer, SwarmTransportClient } from './lib/swarm-transport';