@usewhisper/mcp-server 2.5.0 → 2.7.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.
- package/README.md +17 -0
- package/dist/server.js +494 -93
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -12,6 +12,13 @@ Whisper MCP is the universal context bridge for coding agents. It connects Claud
|
|
|
12
12
|
- Scoped MCP config generator: `whisper-context-mcp scope ...`
|
|
13
13
|
- Tool migration helper: `whisper-context-mcp --print-tool-map`
|
|
14
14
|
|
|
15
|
+
## What's New in v2.6
|
|
16
|
+
|
|
17
|
+
- Automatic project, user, and session scope defaults for MCP retrieval and memory operations.
|
|
18
|
+
- `context.query` can use the automatic agent runtime by default when the caller has not requested manual-only filters.
|
|
19
|
+
- URL and domain-style retrieval requests now isolate matching sources better in shared projects.
|
|
20
|
+
- MCP memory operations now default to pending/session-aware reads and writes when compatible.
|
|
21
|
+
|
|
15
22
|
## Install
|
|
16
23
|
|
|
17
24
|
```text
|
|
@@ -132,6 +139,16 @@ All valid `search` calls return the same top-level payload shape:
|
|
|
132
139
|
}
|
|
133
140
|
```
|
|
134
141
|
|
|
142
|
+
### Automatic Defaults
|
|
143
|
+
|
|
144
|
+
When compatible with the caller’s requested options, Whisper MCP now behaves like an implicit context layer instead of a raw tool database:
|
|
145
|
+
|
|
146
|
+
- `context.query` auto-attaches default project, user, and session scope
|
|
147
|
+
- compatible retrieval calls use automatic runtime-backed pre-retrieval
|
|
148
|
+
- memory writes and session ingest calls attach stable default scope automatically
|
|
149
|
+
|
|
150
|
+
If the caller explicitly requests manual query controls such as `chunk_types`, explicit graph traversal, or other narrow filters, MCP preserves those manual semantics instead of forcing automatic mode.
|
|
151
|
+
|
|
135
152
|
## Source Contract (`context.add_source`)
|
|
136
153
|
|
|
137
154
|
Input:
|
package/dist/server.js
CHANGED
|
@@ -1365,6 +1365,29 @@ ${lines.join("\n")}`;
|
|
|
1365
1365
|
function compactWhitespace(value) {
|
|
1366
1366
|
return value.replace(/\s+/g, " ").trim();
|
|
1367
1367
|
}
|
|
1368
|
+
function normalizeSummary(value) {
|
|
1369
|
+
return compactWhitespace(String(value || "").toLowerCase());
|
|
1370
|
+
}
|
|
1371
|
+
function tokenize(value) {
|
|
1372
|
+
return normalizeSummary(value).split(/[^a-z0-9_./-]+/i).map((token) => token.trim()).filter(Boolean);
|
|
1373
|
+
}
|
|
1374
|
+
function jaccardOverlap(left, right) {
|
|
1375
|
+
const leftTokens = new Set(tokenize(left));
|
|
1376
|
+
const rightTokens = new Set(tokenize(right));
|
|
1377
|
+
if (leftTokens.size === 0 || rightTokens.size === 0) return 0;
|
|
1378
|
+
let intersection = 0;
|
|
1379
|
+
for (const token of leftTokens) {
|
|
1380
|
+
if (rightTokens.has(token)) intersection += 1;
|
|
1381
|
+
}
|
|
1382
|
+
const union = (/* @__PURE__ */ new Set([...leftTokens, ...rightTokens])).size;
|
|
1383
|
+
return union > 0 ? intersection / union : 0;
|
|
1384
|
+
}
|
|
1385
|
+
function clamp01(value) {
|
|
1386
|
+
if (!Number.isFinite(value)) return 0;
|
|
1387
|
+
if (value < 0) return 0;
|
|
1388
|
+
if (value > 1) return 1;
|
|
1389
|
+
return value;
|
|
1390
|
+
}
|
|
1368
1391
|
function withTimeout(promise, timeoutMs) {
|
|
1369
1392
|
return new Promise((resolve, reject) => {
|
|
1370
1393
|
const timeout = setTimeout(() => {
|
|
@@ -1398,39 +1421,76 @@ function extractTimestamp(metadata) {
|
|
|
1398
1421
|
}
|
|
1399
1422
|
return 0;
|
|
1400
1423
|
}
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1424
|
+
var DEFAULT_RANK_WEIGHTS = {
|
|
1425
|
+
focusedPassBonus: 0.2,
|
|
1426
|
+
sourceMatchBonus: 0.18,
|
|
1427
|
+
touchedFileBonus: 0.12,
|
|
1428
|
+
clientMatchBonus: 0.1,
|
|
1429
|
+
highSalienceBonus: 0.12,
|
|
1430
|
+
mediumSalienceBonus: 0.06,
|
|
1431
|
+
staleBroadPenalty: -0.1,
|
|
1432
|
+
unrelatedClientPenalty: -0.18,
|
|
1433
|
+
lowSaliencePenalty: -0.12
|
|
1434
|
+
};
|
|
1435
|
+
var DEFAULT_SOURCE_ACTIVITY = {
|
|
1436
|
+
maxTurns: 10,
|
|
1437
|
+
maxIdleMs: 30 * 60 * 1e3,
|
|
1438
|
+
decayAfterTurns: 5,
|
|
1439
|
+
decayAfterIdleMs: 15 * 60 * 1e3,
|
|
1440
|
+
evictOnTaskSwitch: true
|
|
1441
|
+
};
|
|
1407
1442
|
var WhisperAgentRuntime = class {
|
|
1408
1443
|
constructor(args) {
|
|
1409
1444
|
this.args = args;
|
|
1410
1445
|
this.bindingStore = createBindingStore(args.options.bindingStorePath);
|
|
1411
|
-
|
|
1446
|
+
const retrieval = args.options.retrieval || {};
|
|
1447
|
+
this.focusedTopK = retrieval.focusedTopK ?? args.options.topK ?? 6;
|
|
1448
|
+
this.broadTopK = retrieval.broadTopK ?? Math.max(args.options.topK ?? 6, 10);
|
|
1412
1449
|
this.maxTokens = args.options.maxTokens ?? 4e3;
|
|
1413
1450
|
this.targetRetrievalMs = args.options.targetRetrievalMs ?? 2500;
|
|
1414
1451
|
this.hardRetrievalTimeoutMs = args.options.hardRetrievalTimeoutMs ?? 4e3;
|
|
1415
1452
|
this.recentWorkLimit = args.options.recentWorkLimit ?? 40;
|
|
1416
1453
|
this.baseContext = args.baseContext;
|
|
1417
1454
|
this.clientName = args.baseContext.clientName || "whisper-agent-runtime";
|
|
1455
|
+
this.minFocusedResults = retrieval.minFocusedResults ?? 3;
|
|
1456
|
+
this.minFocusedTopScore = retrieval.minFocusedTopScore ?? 0.55;
|
|
1457
|
+
this.minProjectScore = retrieval.minProjectScore ?? 0.5;
|
|
1458
|
+
this.minMemoryScore = retrieval.minMemoryScore ?? 0.6;
|
|
1459
|
+
this.rankWeights = { ...DEFAULT_RANK_WEIGHTS, ...retrieval.rankWeights || {} };
|
|
1460
|
+
this.sourceActivityOptions = { ...DEFAULT_SOURCE_ACTIVITY, ...retrieval.sourceActivity || {} };
|
|
1418
1461
|
}
|
|
1419
1462
|
bindingStore;
|
|
1420
|
-
|
|
1463
|
+
focusedTopK;
|
|
1464
|
+
broadTopK;
|
|
1421
1465
|
maxTokens;
|
|
1422
1466
|
targetRetrievalMs;
|
|
1423
1467
|
hardRetrievalTimeoutMs;
|
|
1424
1468
|
recentWorkLimit;
|
|
1425
1469
|
baseContext;
|
|
1426
1470
|
clientName;
|
|
1471
|
+
minFocusedResults;
|
|
1472
|
+
minFocusedTopScore;
|
|
1473
|
+
minProjectScore;
|
|
1474
|
+
minMemoryScore;
|
|
1475
|
+
rankWeights;
|
|
1476
|
+
sourceActivityOptions;
|
|
1427
1477
|
bindings = null;
|
|
1428
1478
|
touchedFiles = [];
|
|
1429
1479
|
recentWork = [];
|
|
1480
|
+
recentSourceActivity = [];
|
|
1430
1481
|
bufferedLowSalience = [];
|
|
1431
1482
|
lastPreparedTurn = null;
|
|
1432
1483
|
mergedCount = 0;
|
|
1433
1484
|
droppedCount = 0;
|
|
1485
|
+
focusedPassHits = 0;
|
|
1486
|
+
fallbackTriggers = 0;
|
|
1487
|
+
floorDroppedCount = 0;
|
|
1488
|
+
injectedItemCount = 0;
|
|
1489
|
+
sourceScopedTurns = 0;
|
|
1490
|
+
broadScopedTurns = 0;
|
|
1491
|
+
totalTurns = 0;
|
|
1492
|
+
currentTurn = 0;
|
|
1493
|
+
lastTaskSummary = "";
|
|
1434
1494
|
lastScope = {};
|
|
1435
1495
|
async getBindings() {
|
|
1436
1496
|
if (!this.bindings) {
|
|
@@ -1448,6 +1508,64 @@ var WhisperAgentRuntime = class {
|
|
|
1448
1508
|
pushWorkEvent(event) {
|
|
1449
1509
|
this.recentWork = [...this.recentWork, event].slice(-this.recentWorkLimit);
|
|
1450
1510
|
}
|
|
1511
|
+
noteSourceActivity(sourceIds) {
|
|
1512
|
+
const now = Date.now();
|
|
1513
|
+
for (const sourceId of [...new Set((sourceIds || []).map((value) => String(value || "").trim()).filter(Boolean))]) {
|
|
1514
|
+
this.recentSourceActivity = [
|
|
1515
|
+
...this.recentSourceActivity.filter((entry) => entry.sourceId !== sourceId),
|
|
1516
|
+
{ sourceId, turn: this.currentTurn, at: now }
|
|
1517
|
+
].slice(-24);
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
refreshTaskSummary(taskSummary) {
|
|
1521
|
+
const next = normalizeSummary(taskSummary);
|
|
1522
|
+
if (!next) return;
|
|
1523
|
+
if (this.sourceActivityOptions.evictOnTaskSwitch && this.lastTaskSummary && this.lastTaskSummary !== next && jaccardOverlap(this.lastTaskSummary, next) < 0.6) {
|
|
1524
|
+
this.recentSourceActivity = [];
|
|
1525
|
+
}
|
|
1526
|
+
this.lastTaskSummary = next;
|
|
1527
|
+
}
|
|
1528
|
+
activeSourceIds() {
|
|
1529
|
+
const now = Date.now();
|
|
1530
|
+
const active = /* @__PURE__ */ new Map();
|
|
1531
|
+
const maxTurns = this.sourceActivityOptions.maxTurns;
|
|
1532
|
+
const maxIdleMs = this.sourceActivityOptions.maxIdleMs;
|
|
1533
|
+
const decayAfterTurns = this.sourceActivityOptions.decayAfterTurns;
|
|
1534
|
+
const decayAfterIdleMs = this.sourceActivityOptions.decayAfterIdleMs;
|
|
1535
|
+
const fresh = [];
|
|
1536
|
+
for (const entry of this.recentSourceActivity) {
|
|
1537
|
+
const turnDelta = this.currentTurn - entry.turn;
|
|
1538
|
+
const idleDelta = now - entry.at;
|
|
1539
|
+
if (turnDelta > maxTurns || idleDelta > maxIdleMs) continue;
|
|
1540
|
+
fresh.push(entry);
|
|
1541
|
+
let weight = 1;
|
|
1542
|
+
if (turnDelta > decayAfterTurns || idleDelta > decayAfterIdleMs) {
|
|
1543
|
+
weight = 0.5;
|
|
1544
|
+
}
|
|
1545
|
+
const current = active.get(entry.sourceId) || 0;
|
|
1546
|
+
active.set(entry.sourceId, Math.max(current, weight));
|
|
1547
|
+
}
|
|
1548
|
+
this.recentSourceActivity = fresh.slice(-24);
|
|
1549
|
+
return [...active.entries()].sort((left, right) => right[1] - left[1]).map(([sourceId]) => sourceId).slice(0, 4);
|
|
1550
|
+
}
|
|
1551
|
+
focusedScope(input) {
|
|
1552
|
+
const sourceIds = this.activeSourceIds();
|
|
1553
|
+
const fileHints = [...new Set([
|
|
1554
|
+
...input.touchedFiles || [],
|
|
1555
|
+
...this.touchedFiles,
|
|
1556
|
+
...this.recentWork.flatMap((event) => event.filePaths || [])
|
|
1557
|
+
].map((value) => String(value || "").trim()).filter(Boolean))].slice(-4);
|
|
1558
|
+
return {
|
|
1559
|
+
sourceIds,
|
|
1560
|
+
fileHints,
|
|
1561
|
+
clientName: this.clientName || void 0
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1564
|
+
exactFileMetadataFilter(fileHints) {
|
|
1565
|
+
const exact = fileHints.find((value) => /[\\/]/.test(value));
|
|
1566
|
+
if (!exact) return void 0;
|
|
1567
|
+
return { filePath: exact };
|
|
1568
|
+
}
|
|
1451
1569
|
makeTaskFrameQuery(input) {
|
|
1452
1570
|
const task = compactWhitespace(input.taskSummary || "");
|
|
1453
1571
|
const salient = this.recentWork.filter((event) => event.salience === "high").slice(-3).map((event) => `${event.kind}: ${event.summary}`);
|
|
@@ -1524,23 +1642,29 @@ var WhisperAgentRuntime = class {
|
|
|
1524
1642
|
};
|
|
1525
1643
|
}
|
|
1526
1644
|
}
|
|
1527
|
-
contextItems(result, sourceQuery) {
|
|
1645
|
+
contextItems(result, sourceQuery, pass) {
|
|
1646
|
+
const sourceScope = result.meta?.source_scope;
|
|
1647
|
+
if (sourceScope?.mode === "auto" || sourceScope?.mode === "explicit") {
|
|
1648
|
+
this.noteSourceActivity(sourceScope.source_ids || []);
|
|
1649
|
+
}
|
|
1528
1650
|
return (result.results || []).map((item) => ({
|
|
1529
1651
|
id: item.id,
|
|
1530
1652
|
content: item.content,
|
|
1531
1653
|
type: "project",
|
|
1532
1654
|
score: item.score ?? 0,
|
|
1533
1655
|
sourceQuery,
|
|
1656
|
+
pass,
|
|
1534
1657
|
metadata: item.metadata || {}
|
|
1535
1658
|
}));
|
|
1536
1659
|
}
|
|
1537
|
-
memoryItems(result, sourceQuery) {
|
|
1660
|
+
memoryItems(result, sourceQuery, pass) {
|
|
1538
1661
|
return (result.results || []).map((item, index) => ({
|
|
1539
1662
|
id: item.memory?.id || item.chunk?.id || `${sourceQuery}_memory_${index}`,
|
|
1540
1663
|
content: item.chunk?.content || item.memory?.content || "",
|
|
1541
1664
|
type: "memory",
|
|
1542
1665
|
score: item.similarity ?? 0,
|
|
1543
1666
|
sourceQuery,
|
|
1667
|
+
pass,
|
|
1544
1668
|
metadata: {
|
|
1545
1669
|
...item.chunk?.metadata || {},
|
|
1546
1670
|
...item.memory?.temporal || {},
|
|
@@ -1548,22 +1672,99 @@ var WhisperAgentRuntime = class {
|
|
|
1548
1672
|
}
|
|
1549
1673
|
})).filter((item) => item.content);
|
|
1550
1674
|
}
|
|
1551
|
-
|
|
1675
|
+
stableItemKey(item) {
|
|
1676
|
+
const metadata = item.metadata || {};
|
|
1677
|
+
const sourceId = String(metadata.source_id || "");
|
|
1678
|
+
const documentId = String(metadata.document_id || metadata.documentId || "");
|
|
1679
|
+
const chunkId = String(metadata.chunk_id || metadata.chunkId || item.id || "");
|
|
1680
|
+
return stableHash(`${sourceId}|${documentId}|${chunkId}|${item.content.slice(0, 256)}`);
|
|
1681
|
+
}
|
|
1682
|
+
metadataStrings(item) {
|
|
1683
|
+
const metadata = item.metadata || {};
|
|
1684
|
+
return [
|
|
1685
|
+
metadata.filePath,
|
|
1686
|
+
metadata.file_path,
|
|
1687
|
+
metadata.path,
|
|
1688
|
+
metadata.section_path,
|
|
1689
|
+
metadata.parent_section_path,
|
|
1690
|
+
metadata.web_url,
|
|
1691
|
+
metadata.url
|
|
1692
|
+
].map((value) => String(value || "").toLowerCase()).filter(Boolean);
|
|
1693
|
+
}
|
|
1694
|
+
hasSourceMatch(item, scope) {
|
|
1695
|
+
const sourceId = String(item.metadata?.source_id || "");
|
|
1696
|
+
return Boolean(sourceId && scope.sourceIds.includes(sourceId));
|
|
1697
|
+
}
|
|
1698
|
+
hasFileMatch(item, scope) {
|
|
1699
|
+
if (scope.fileHints.length === 0) return false;
|
|
1700
|
+
const metadata = this.metadataStrings(item);
|
|
1701
|
+
const lowerHints = scope.fileHints.map((hint) => hint.toLowerCase());
|
|
1702
|
+
return lowerHints.some((hint) => {
|
|
1703
|
+
const base = pathBase(hint).toLowerCase();
|
|
1704
|
+
return metadata.some((value) => value.includes(hint) || value.endsWith(base));
|
|
1705
|
+
});
|
|
1706
|
+
}
|
|
1707
|
+
hasClientMatch(item, scope) {
|
|
1708
|
+
const itemClient = String(item.metadata?.client_name || "");
|
|
1709
|
+
return Boolean(scope.clientName && itemClient && itemClient === scope.clientName);
|
|
1710
|
+
}
|
|
1711
|
+
salienceAdjustment(item) {
|
|
1712
|
+
const salience = item.metadata?.salience;
|
|
1713
|
+
if (salience === "high") return this.rankWeights.highSalienceBonus;
|
|
1714
|
+
if (salience === "medium") return this.rankWeights.mediumSalienceBonus;
|
|
1715
|
+
if (salience === "low") return this.rankWeights.lowSaliencePenalty;
|
|
1716
|
+
return 0;
|
|
1717
|
+
}
|
|
1718
|
+
narrowFocusedMemories(items, scope) {
|
|
1719
|
+
const hasSignals = scope.sourceIds.length > 0 || scope.fileHints.length > 0 || Boolean(scope.clientName);
|
|
1720
|
+
if (!hasSignals) return items;
|
|
1721
|
+
const narrowed = items.filter((item) => {
|
|
1722
|
+
const matchesClient = this.hasClientMatch(item, scope);
|
|
1723
|
+
const matchesFile = this.hasFileMatch(item, scope);
|
|
1724
|
+
const matchesSource = this.hasSourceMatch(item, scope);
|
|
1725
|
+
const salience = item.metadata?.salience;
|
|
1726
|
+
if (scope.clientName && item.metadata?.client_name && !matchesClient) {
|
|
1727
|
+
return false;
|
|
1728
|
+
}
|
|
1729
|
+
if (salience === "low" && !matchesFile && !matchesSource) {
|
|
1730
|
+
return false;
|
|
1731
|
+
}
|
|
1732
|
+
return matchesClient || matchesFile || matchesSource || !scope.clientName;
|
|
1733
|
+
});
|
|
1734
|
+
return narrowed.length > 0 ? narrowed : items;
|
|
1735
|
+
}
|
|
1736
|
+
applyRelevanceFloor(items) {
|
|
1737
|
+
const filtered = items.filter(
|
|
1738
|
+
(item) => item.type === "project" ? item.score >= this.minProjectScore : item.score >= this.minMemoryScore
|
|
1739
|
+
);
|
|
1740
|
+
return { items: filtered, dropped: Math.max(0, items.length - filtered.length) };
|
|
1741
|
+
}
|
|
1742
|
+
rerank(items, scope) {
|
|
1552
1743
|
const deduped = /* @__PURE__ */ new Map();
|
|
1553
1744
|
for (const item of items) {
|
|
1554
|
-
const key =
|
|
1745
|
+
const key = this.stableItemKey(item);
|
|
1555
1746
|
const recency = extractTimestamp(item.metadata) > 0 ? 0.04 : 0;
|
|
1556
1747
|
const queryBonus = item.sourceQuery === "primary" ? 0.08 : item.sourceQuery === "task_frame" ? 0.04 : 0.03;
|
|
1748
|
+
const sourceMatch = this.hasSourceMatch(item, scope);
|
|
1749
|
+
const fileMatch = this.hasFileMatch(item, scope);
|
|
1750
|
+
const clientMatch = this.hasClientMatch(item, scope);
|
|
1751
|
+
const broadPenalty = item.pass === "broad" && !sourceMatch && !fileMatch && !clientMatch ? this.rankWeights.staleBroadPenalty : 0;
|
|
1752
|
+
const clientPenalty = scope.clientName && item.metadata?.client_name && !clientMatch ? this.rankWeights.unrelatedClientPenalty : 0;
|
|
1557
1753
|
const next = {
|
|
1558
1754
|
...item,
|
|
1559
|
-
score:
|
|
1755
|
+
score: clamp01(
|
|
1756
|
+
item.score + queryBonus + recency + (item.pass === "focused" ? this.rankWeights.focusedPassBonus : 0) + (sourceMatch ? this.rankWeights.sourceMatchBonus : 0) + (fileMatch ? this.rankWeights.touchedFileBonus : 0) + (clientMatch ? this.rankWeights.clientMatchBonus : 0) + this.salienceAdjustment(item) + broadPenalty + clientPenalty
|
|
1757
|
+
)
|
|
1560
1758
|
};
|
|
1561
1759
|
const existing = deduped.get(key);
|
|
1562
1760
|
if (!existing || next.score > existing.score) {
|
|
1563
1761
|
deduped.set(key, next);
|
|
1564
1762
|
}
|
|
1565
1763
|
}
|
|
1566
|
-
return
|
|
1764
|
+
return {
|
|
1765
|
+
items: [...deduped.values()].sort((left, right) => right.score - left.score),
|
|
1766
|
+
dedupedCount: Math.max(0, items.length - deduped.size)
|
|
1767
|
+
};
|
|
1567
1768
|
}
|
|
1568
1769
|
buildContext(items) {
|
|
1569
1770
|
const maxChars = this.maxTokens * 4;
|
|
@@ -1602,7 +1803,7 @@ ${lines.join("\n")}`;
|
|
|
1602
1803
|
this.runBranch("project_rules", () => this.args.adapter.query({
|
|
1603
1804
|
project: scope.project,
|
|
1604
1805
|
query: "project rules instructions constraints conventions open threads",
|
|
1605
|
-
top_k: this.
|
|
1806
|
+
top_k: this.focusedTopK,
|
|
1606
1807
|
include_memories: false,
|
|
1607
1808
|
user_id: scope.userId,
|
|
1608
1809
|
session_id: scope.sessionId,
|
|
@@ -1620,7 +1821,7 @@ ${lines.join("\n")}`;
|
|
|
1620
1821
|
continue;
|
|
1621
1822
|
}
|
|
1622
1823
|
if (branch.name === "project_rules") {
|
|
1623
|
-
items.push(...this.contextItems(branch.value, "bootstrap"));
|
|
1824
|
+
items.push(...this.contextItems(branch.value, "bootstrap", "bootstrap"));
|
|
1624
1825
|
continue;
|
|
1625
1826
|
}
|
|
1626
1827
|
const records = branch.value.memories || [];
|
|
@@ -1630,10 +1831,12 @@ ${lines.join("\n")}`;
|
|
|
1630
1831
|
type: "memory",
|
|
1631
1832
|
score: 0.4,
|
|
1632
1833
|
sourceQuery: "bootstrap",
|
|
1834
|
+
pass: "bootstrap",
|
|
1633
1835
|
metadata: memory
|
|
1634
1836
|
})).filter((item) => item.content));
|
|
1635
1837
|
}
|
|
1636
|
-
const
|
|
1838
|
+
const reranked = this.rerank(items, { sourceIds: [], fileHints: [], clientName: this.clientName });
|
|
1839
|
+
const ranked = reranked.items.slice(0, this.broadTopK * 2);
|
|
1637
1840
|
const prepared = {
|
|
1638
1841
|
scope,
|
|
1639
1842
|
retrieval: {
|
|
@@ -1645,7 +1848,14 @@ ${lines.join("\n")}`;
|
|
|
1645
1848
|
durationMs: Date.now() - startedAt,
|
|
1646
1849
|
targetBudgetMs: this.targetRetrievalMs,
|
|
1647
1850
|
hardTimeoutMs: this.hardRetrievalTimeoutMs,
|
|
1648
|
-
branchStatus
|
|
1851
|
+
branchStatus,
|
|
1852
|
+
focusedScopeApplied: false,
|
|
1853
|
+
focusedSourceIds: [],
|
|
1854
|
+
focusedFileHints: [],
|
|
1855
|
+
clientScoped: false,
|
|
1856
|
+
fallbackUsed: false,
|
|
1857
|
+
droppedBelowFloor: 0,
|
|
1858
|
+
dedupedCount: reranked.dedupedCount
|
|
1649
1859
|
},
|
|
1650
1860
|
context: this.buildContext(ranked),
|
|
1651
1861
|
items: ranked
|
|
@@ -1654,100 +1864,195 @@ ${lines.join("\n")}`;
|
|
|
1654
1864
|
return prepared;
|
|
1655
1865
|
}
|
|
1656
1866
|
async beforeTurn(input, context = {}) {
|
|
1867
|
+
this.currentTurn += 1;
|
|
1657
1868
|
this.pushTouchedFiles(input.touchedFiles);
|
|
1869
|
+
this.refreshTaskSummary(input.taskSummary);
|
|
1658
1870
|
const { scope, warning } = await this.resolveScope(context);
|
|
1659
1871
|
const primaryQuery = compactWhitespace(input.userMessage);
|
|
1660
1872
|
const taskFrameQuery = this.makeTaskFrameQuery(input);
|
|
1873
|
+
const focusedScope = this.focusedScope(input);
|
|
1874
|
+
const focusedMetadataFilter = this.exactFileMetadataFilter(focusedScope.fileHints);
|
|
1875
|
+
const focusedScopeApplied = focusedScope.sourceIds.length > 0 || focusedScope.fileHints.length > 0 || Boolean(focusedScope.clientName);
|
|
1661
1876
|
const warnings = warning ? [warning] : [];
|
|
1662
1877
|
const startedAt = Date.now();
|
|
1663
|
-
const
|
|
1664
|
-
|
|
1878
|
+
const branchStatus = {};
|
|
1879
|
+
const collectFromBranches = (branches, pass) => {
|
|
1880
|
+
const collected = [];
|
|
1881
|
+
let okCount = 0;
|
|
1882
|
+
for (const branch of branches) {
|
|
1883
|
+
branchStatus[branch.name] = branch.status;
|
|
1884
|
+
if (branch.status !== "ok") {
|
|
1885
|
+
if (branch.status !== "skipped" && branch.reason) warnings.push(`${branch.name}:${branch.reason}`);
|
|
1886
|
+
continue;
|
|
1887
|
+
}
|
|
1888
|
+
okCount += 1;
|
|
1889
|
+
if (branch.name.startsWith("context")) {
|
|
1890
|
+
collected.push(...this.contextItems(
|
|
1891
|
+
branch.value,
|
|
1892
|
+
branch.name.includes("task_frame") ? "task_frame" : "primary",
|
|
1893
|
+
pass
|
|
1894
|
+
));
|
|
1895
|
+
} else {
|
|
1896
|
+
const memoryItems = this.memoryItems(
|
|
1897
|
+
branch.value,
|
|
1898
|
+
branch.name.includes("task_frame") ? "task_frame" : "primary",
|
|
1899
|
+
pass
|
|
1900
|
+
);
|
|
1901
|
+
collected.push(...pass === "focused" ? this.narrowFocusedMemories(memoryItems, focusedScope) : memoryItems);
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
return { collected, okCount };
|
|
1905
|
+
};
|
|
1906
|
+
const focusedBranches = await Promise.all([
|
|
1907
|
+
this.runBranch("context_primary_focused", () => this.args.adapter.query({
|
|
1665
1908
|
project: scope.project,
|
|
1666
1909
|
query: primaryQuery,
|
|
1667
|
-
top_k: this.
|
|
1910
|
+
top_k: this.focusedTopK,
|
|
1668
1911
|
include_memories: false,
|
|
1669
1912
|
user_id: scope.userId,
|
|
1670
1913
|
session_id: scope.sessionId,
|
|
1914
|
+
source_ids: focusedScope.sourceIds.length > 0 ? focusedScope.sourceIds : void 0,
|
|
1915
|
+
metadata_filter: focusedMetadataFilter,
|
|
1671
1916
|
max_tokens: this.maxTokens,
|
|
1672
1917
|
compress: true,
|
|
1673
1918
|
compression_strategy: "adaptive"
|
|
1674
1919
|
})),
|
|
1675
|
-
this.runBranch("
|
|
1920
|
+
this.runBranch("memory_primary_focused", () => this.args.adapter.searchMemories({
|
|
1676
1921
|
project: scope.project,
|
|
1677
1922
|
query: primaryQuery,
|
|
1678
1923
|
user_id: scope.userId,
|
|
1679
1924
|
session_id: scope.sessionId,
|
|
1680
|
-
top_k: this.
|
|
1925
|
+
top_k: this.focusedTopK,
|
|
1681
1926
|
include_pending: true,
|
|
1682
1927
|
profile: "balanced"
|
|
1683
1928
|
})),
|
|
1684
|
-
taskFrameQuery ? this.runBranch("
|
|
1929
|
+
taskFrameQuery ? this.runBranch("context_task_frame_focused", () => this.args.adapter.query({
|
|
1685
1930
|
project: scope.project,
|
|
1686
1931
|
query: taskFrameQuery,
|
|
1687
|
-
top_k: this.
|
|
1932
|
+
top_k: this.focusedTopK,
|
|
1688
1933
|
include_memories: false,
|
|
1689
1934
|
user_id: scope.userId,
|
|
1690
1935
|
session_id: scope.sessionId,
|
|
1936
|
+
source_ids: focusedScope.sourceIds.length > 0 ? focusedScope.sourceIds : void 0,
|
|
1937
|
+
metadata_filter: focusedMetadataFilter,
|
|
1691
1938
|
max_tokens: this.maxTokens,
|
|
1692
1939
|
compress: true,
|
|
1693
1940
|
compression_strategy: "adaptive"
|
|
1694
|
-
})) : Promise.resolve({
|
|
1695
|
-
|
|
1696
|
-
status: "skipped",
|
|
1697
|
-
durationMs: 0
|
|
1698
|
-
}),
|
|
1699
|
-
taskFrameQuery ? this.runBranch("memory_task_frame", () => this.args.adapter.searchMemories({
|
|
1941
|
+
})) : Promise.resolve({ name: "context_task_frame_focused", status: "skipped", durationMs: 0 }),
|
|
1942
|
+
taskFrameQuery ? this.runBranch("memory_task_frame_focused", () => this.args.adapter.searchMemories({
|
|
1700
1943
|
project: scope.project,
|
|
1701
1944
|
query: taskFrameQuery,
|
|
1702
1945
|
user_id: scope.userId,
|
|
1703
1946
|
session_id: scope.sessionId,
|
|
1704
|
-
top_k: this.
|
|
1947
|
+
top_k: this.focusedTopK,
|
|
1705
1948
|
include_pending: true,
|
|
1706
1949
|
profile: "balanced"
|
|
1707
|
-
})) : Promise.resolve({
|
|
1708
|
-
name: "memory_task_frame",
|
|
1709
|
-
status: "skipped",
|
|
1710
|
-
durationMs: 0
|
|
1711
|
-
})
|
|
1950
|
+
})) : Promise.resolve({ name: "memory_task_frame_focused", status: "skipped", durationMs: 0 })
|
|
1712
1951
|
]);
|
|
1713
|
-
const
|
|
1714
|
-
const
|
|
1715
|
-
|
|
1716
|
-
|
|
1952
|
+
const focusedCollected = collectFromBranches(focusedBranches, "focused");
|
|
1953
|
+
const focusedRanked = this.rerank(focusedCollected.collected, focusedScope);
|
|
1954
|
+
const focusedFloored = this.applyRelevanceFloor(focusedRanked.items);
|
|
1955
|
+
let allCollected = [...focusedFloored.items];
|
|
1956
|
+
let totalOkCount = focusedCollected.okCount;
|
|
1957
|
+
let dedupedCount = focusedRanked.dedupedCount;
|
|
1958
|
+
let droppedBelowFloor = focusedFloored.dropped;
|
|
1959
|
+
const focusedTopScore = focusedFloored.items[0]?.score ?? 0;
|
|
1960
|
+
const fallbackUsed = focusedFloored.items.length < this.minFocusedResults || focusedTopScore < this.minFocusedTopScore;
|
|
1961
|
+
if (focusedScopeApplied) {
|
|
1962
|
+
this.sourceScopedTurns += 1;
|
|
1963
|
+
}
|
|
1964
|
+
if (!fallbackUsed) {
|
|
1965
|
+
this.focusedPassHits += 1;
|
|
1966
|
+
}
|
|
1967
|
+
const broadBranches = fallbackUsed ? await Promise.all([
|
|
1968
|
+
this.runBranch("context_primary_broad", () => this.args.adapter.query({
|
|
1969
|
+
project: scope.project,
|
|
1970
|
+
query: primaryQuery,
|
|
1971
|
+
top_k: this.broadTopK,
|
|
1972
|
+
include_memories: false,
|
|
1973
|
+
user_id: scope.userId,
|
|
1974
|
+
session_id: scope.sessionId,
|
|
1975
|
+
max_tokens: this.maxTokens,
|
|
1976
|
+
compress: true,
|
|
1977
|
+
compression_strategy: "adaptive"
|
|
1978
|
+
})),
|
|
1979
|
+
this.runBranch("memory_primary_broad", () => this.args.adapter.searchMemories({
|
|
1980
|
+
project: scope.project,
|
|
1981
|
+
query: primaryQuery,
|
|
1982
|
+
user_id: scope.userId,
|
|
1983
|
+
session_id: scope.sessionId,
|
|
1984
|
+
top_k: this.broadTopK,
|
|
1985
|
+
include_pending: true,
|
|
1986
|
+
profile: "balanced"
|
|
1987
|
+
})),
|
|
1988
|
+
taskFrameQuery ? this.runBranch("context_task_frame_broad", () => this.args.adapter.query({
|
|
1989
|
+
project: scope.project,
|
|
1990
|
+
query: taskFrameQuery,
|
|
1991
|
+
top_k: this.broadTopK,
|
|
1992
|
+
include_memories: false,
|
|
1993
|
+
user_id: scope.userId,
|
|
1994
|
+
session_id: scope.sessionId,
|
|
1995
|
+
max_tokens: this.maxTokens,
|
|
1996
|
+
compress: true,
|
|
1997
|
+
compression_strategy: "adaptive"
|
|
1998
|
+
})) : Promise.resolve({ name: "context_task_frame_broad", status: "skipped", durationMs: 0 }),
|
|
1999
|
+
taskFrameQuery ? this.runBranch("memory_task_frame_broad", () => this.args.adapter.searchMemories({
|
|
2000
|
+
project: scope.project,
|
|
2001
|
+
query: taskFrameQuery,
|
|
2002
|
+
user_id: scope.userId,
|
|
2003
|
+
session_id: scope.sessionId,
|
|
2004
|
+
top_k: this.broadTopK,
|
|
2005
|
+
include_pending: true,
|
|
2006
|
+
profile: "balanced"
|
|
2007
|
+
})) : Promise.resolve({ name: "memory_task_frame_broad", status: "skipped", durationMs: 0 })
|
|
2008
|
+
]) : [
|
|
2009
|
+
{ name: "context_primary_broad", status: "skipped", durationMs: 0 },
|
|
2010
|
+
{ name: "memory_primary_broad", status: "skipped", durationMs: 0 },
|
|
2011
|
+
{ name: "context_task_frame_broad", status: "skipped", durationMs: 0 },
|
|
2012
|
+
{ name: "memory_task_frame_broad", status: "skipped", durationMs: 0 }
|
|
2013
|
+
];
|
|
2014
|
+
const broadCollected = collectFromBranches(broadBranches, "broad");
|
|
2015
|
+
totalOkCount += broadCollected.okCount;
|
|
2016
|
+
if (fallbackUsed) {
|
|
2017
|
+
this.fallbackTriggers += 1;
|
|
2018
|
+
this.broadScopedTurns += 1;
|
|
2019
|
+
allCollected = [...allCollected, ...broadCollected.collected];
|
|
2020
|
+
}
|
|
2021
|
+
const ranked = this.rerank(allCollected, focusedScope);
|
|
2022
|
+
dedupedCount += ranked.dedupedCount;
|
|
2023
|
+
const floored = this.applyRelevanceFloor(ranked.items);
|
|
2024
|
+
droppedBelowFloor += floored.dropped;
|
|
2025
|
+
this.floorDroppedCount += droppedBelowFloor;
|
|
2026
|
+
this.droppedCount += droppedBelowFloor;
|
|
2027
|
+
const finalItems = floored.items.slice(0, this.broadTopK);
|
|
2028
|
+
this.injectedItemCount += finalItems.length;
|
|
2029
|
+
this.totalTurns += 1;
|
|
2030
|
+
const executedBranches = [...focusedBranches, ...broadBranches].filter((branch) => branch.status !== "skipped");
|
|
2031
|
+
for (const branch of [...focusedBranches, ...broadBranches]) {
|
|
1717
2032
|
branchStatus[branch.name] = branch.status;
|
|
1718
|
-
if (branch.status !== "ok") {
|
|
1719
|
-
if (branch.status !== "skipped" && branch.reason) warnings.push(`${branch.name}:${branch.reason}`);
|
|
1720
|
-
continue;
|
|
1721
|
-
}
|
|
1722
|
-
okCount += 1;
|
|
1723
|
-
if (branch.name.startsWith("context")) {
|
|
1724
|
-
collected.push(...this.contextItems(
|
|
1725
|
-
branch.value,
|
|
1726
|
-
branch.name.includes("task_frame") ? "task_frame" : "primary"
|
|
1727
|
-
));
|
|
1728
|
-
} else {
|
|
1729
|
-
collected.push(...this.memoryItems(
|
|
1730
|
-
branch.value,
|
|
1731
|
-
branch.name.includes("task_frame") ? "task_frame" : "primary"
|
|
1732
|
-
));
|
|
1733
|
-
}
|
|
1734
2033
|
}
|
|
1735
|
-
const ranked = this.rerank(collected).slice(0, this.topK * 2);
|
|
1736
2034
|
const prepared = {
|
|
1737
2035
|
scope,
|
|
1738
2036
|
retrieval: {
|
|
1739
2037
|
primaryQuery,
|
|
1740
2038
|
taskFrameQuery,
|
|
1741
2039
|
warnings,
|
|
1742
|
-
degraded:
|
|
1743
|
-
degradedReason:
|
|
2040
|
+
degraded: totalOkCount < executedBranches.length,
|
|
2041
|
+
degradedReason: totalOkCount === 0 ? "all_retrieval_failed" : warnings.length > 0 ? "partial_retrieval_failed" : void 0,
|
|
1744
2042
|
durationMs: Date.now() - startedAt,
|
|
1745
2043
|
targetBudgetMs: this.targetRetrievalMs,
|
|
1746
2044
|
hardTimeoutMs: this.hardRetrievalTimeoutMs,
|
|
1747
|
-
branchStatus
|
|
2045
|
+
branchStatus,
|
|
2046
|
+
focusedScopeApplied,
|
|
2047
|
+
focusedSourceIds: focusedScope.sourceIds,
|
|
2048
|
+
focusedFileHints: focusedScope.fileHints.map((value) => pathBase(value)),
|
|
2049
|
+
clientScoped: Boolean(focusedScope.clientName),
|
|
2050
|
+
fallbackUsed,
|
|
2051
|
+
droppedBelowFloor,
|
|
2052
|
+
dedupedCount
|
|
1748
2053
|
},
|
|
1749
|
-
context: this.buildContext(
|
|
1750
|
-
items:
|
|
2054
|
+
context: this.buildContext(finalItems),
|
|
2055
|
+
items: finalItems
|
|
1751
2056
|
};
|
|
1752
2057
|
this.lastPreparedTurn = prepared.retrieval;
|
|
1753
2058
|
return prepared;
|
|
@@ -1842,7 +2147,14 @@ ${lines.join("\n")}`;
|
|
|
1842
2147
|
counters: {
|
|
1843
2148
|
mergedCount: this.mergedCount,
|
|
1844
2149
|
droppedCount: this.droppedCount,
|
|
1845
|
-
bufferedLowSalience: this.bufferedLowSalience.length
|
|
2150
|
+
bufferedLowSalience: this.bufferedLowSalience.length,
|
|
2151
|
+
focusedPassHits: this.focusedPassHits,
|
|
2152
|
+
fallbackTriggers: this.fallbackTriggers,
|
|
2153
|
+
floorDroppedCount: this.floorDroppedCount,
|
|
2154
|
+
injectedItemCount: this.injectedItemCount,
|
|
2155
|
+
sourceScopedTurns: this.sourceScopedTurns,
|
|
2156
|
+
broadScopedTurns: this.broadScopedTurns,
|
|
2157
|
+
totalTurns: this.totalTurns
|
|
1846
2158
|
}
|
|
1847
2159
|
};
|
|
1848
2160
|
}
|
|
@@ -3421,9 +3733,10 @@ function createWhisperMcpRuntimeClient(options) {
|
|
|
3421
3733
|
}
|
|
3422
3734
|
var whisper = createWhisperMcpClient();
|
|
3423
3735
|
var runtimeClient = createWhisperMcpRuntimeClient();
|
|
3736
|
+
var runtimeSessions = /* @__PURE__ */ new Map();
|
|
3424
3737
|
var server = new McpServer({
|
|
3425
3738
|
name: "whisper-context",
|
|
3426
|
-
version: "0.
|
|
3739
|
+
version: "0.3.0"
|
|
3427
3740
|
});
|
|
3428
3741
|
function createMcpServer() {
|
|
3429
3742
|
return server;
|
|
@@ -3485,7 +3798,7 @@ function getWorkspaceIdForPath(path, workspaceId) {
|
|
|
3485
3798
|
const seed = `${path}|${DEFAULT_PROJECT || "default"}|${API_KEY.slice(0, 12)}`;
|
|
3486
3799
|
return createHash("sha256").update(seed).digest("hex").slice(0, 20);
|
|
3487
3800
|
}
|
|
3488
|
-
function
|
|
3801
|
+
function clamp012(value) {
|
|
3489
3802
|
if (Number.isNaN(value)) return 0;
|
|
3490
3803
|
if (value < 0) return 0;
|
|
3491
3804
|
if (value > 1) return 1;
|
|
@@ -3531,7 +3844,7 @@ function toEvidenceRef(source, workspaceId, methodFallback) {
|
|
|
3531
3844
|
line_start: lineStart,
|
|
3532
3845
|
...lineEnd ? { line_end: lineEnd } : {},
|
|
3533
3846
|
snippet: String(source.content || metadata.snippet || "").slice(0, 500),
|
|
3534
|
-
score:
|
|
3847
|
+
score: clamp012(Number(source.score ?? metadata.score ?? 0)),
|
|
3535
3848
|
retrieval_method: retrievalMethod,
|
|
3536
3849
|
indexed_at: String(metadata.indexed_at || (/* @__PURE__ */ new Date()).toISOString()),
|
|
3537
3850
|
...metadata.commit ? { commit: String(metadata.commit) } : {},
|
|
@@ -3625,18 +3938,55 @@ async function prepareAutomaticQuery(params) {
|
|
|
3625
3938
|
throw new Error("Whisper runtime client unavailable.");
|
|
3626
3939
|
}
|
|
3627
3940
|
const scope = resolveMcpScope(params);
|
|
3628
|
-
const
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
workspacePath
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3941
|
+
const key = [
|
|
3942
|
+
params.project || DEFAULT_PROJECT || "",
|
|
3943
|
+
scope.userId,
|
|
3944
|
+
scope.sessionId,
|
|
3945
|
+
scope.workspacePath || process.cwd(),
|
|
3946
|
+
String(params.top_k || 10)
|
|
3947
|
+
].join("|");
|
|
3948
|
+
let runtime = runtimeSessions.get(key);
|
|
3949
|
+
if (!runtime) {
|
|
3950
|
+
runtime = runtimeClient.createAgentRuntime({
|
|
3951
|
+
project: params.project,
|
|
3952
|
+
userId: scope.userId,
|
|
3953
|
+
sessionId: scope.sessionId,
|
|
3954
|
+
workspacePath: scope.workspacePath,
|
|
3955
|
+
topK: params.top_k,
|
|
3956
|
+
clientName: "whisper-mcp"
|
|
3957
|
+
});
|
|
3958
|
+
runtimeSessions.set(key, runtime);
|
|
3959
|
+
}
|
|
3636
3960
|
return runtime.beforeTurn({
|
|
3637
3961
|
userMessage: params.query
|
|
3638
3962
|
});
|
|
3639
3963
|
}
|
|
3964
|
+
function noteAutomaticSourceActivity(params) {
|
|
3965
|
+
if (!runtimeClient) return;
|
|
3966
|
+
const sourceIds = [...new Set((params.sourceIds || []).map((value) => String(value || "").trim()).filter(Boolean))];
|
|
3967
|
+
if (sourceIds.length === 0) return;
|
|
3968
|
+
const scope = resolveMcpScope(params);
|
|
3969
|
+
const key = [
|
|
3970
|
+
params.project || DEFAULT_PROJECT || "",
|
|
3971
|
+
scope.userId,
|
|
3972
|
+
scope.sessionId,
|
|
3973
|
+
scope.workspacePath || process.cwd(),
|
|
3974
|
+
String(params.top_k || 10)
|
|
3975
|
+
].join("|");
|
|
3976
|
+
let runtime = runtimeSessions.get(key);
|
|
3977
|
+
if (!runtime) {
|
|
3978
|
+
runtime = runtimeClient.createAgentRuntime({
|
|
3979
|
+
project: params.project,
|
|
3980
|
+
userId: scope.userId,
|
|
3981
|
+
sessionId: scope.sessionId,
|
|
3982
|
+
workspacePath: scope.workspacePath,
|
|
3983
|
+
topK: params.top_k,
|
|
3984
|
+
clientName: "whisper-mcp"
|
|
3985
|
+
});
|
|
3986
|
+
runtimeSessions.set(key, runtime);
|
|
3987
|
+
}
|
|
3988
|
+
runtime.noteSourceActivity(sourceIds);
|
|
3989
|
+
}
|
|
3640
3990
|
function buildAbstain(args) {
|
|
3641
3991
|
return {
|
|
3642
3992
|
status: "abstained",
|
|
@@ -4476,29 +4826,67 @@ server.tool(
|
|
|
4476
4826
|
}
|
|
4477
4827
|
const automaticMode = include_memories !== false && include_graph !== true && !(chunk_types && chunk_types.length > 0) && max_tokens === void 0 && runtimeClient;
|
|
4478
4828
|
if (automaticMode) {
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4829
|
+
try {
|
|
4830
|
+
const prepared = await prepareAutomaticQuery({
|
|
4831
|
+
project: resolvedProject,
|
|
4832
|
+
query,
|
|
4833
|
+
top_k,
|
|
4834
|
+
user_id,
|
|
4835
|
+
session_id
|
|
4836
|
+
});
|
|
4837
|
+
if (!prepared.items.length) {
|
|
4838
|
+
return { content: [{ type: "text", text: "No relevant context found." }] };
|
|
4839
|
+
}
|
|
4840
|
+
const warnings = prepared.retrieval.warnings.length ? `
|
|
4490
4841
|
|
|
4491
4842
|
[automatic_runtime]
|
|
4492
4843
|
${prepared.retrieval.warnings.join("\n")}` : "";
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4844
|
+
const diagnostics = [
|
|
4845
|
+
`focused_scope=${prepared.retrieval.focusedScopeApplied}`,
|
|
4846
|
+
`fallback_used=${prepared.retrieval.fallbackUsed}`,
|
|
4847
|
+
`deduped=${prepared.retrieval.dedupedCount}`,
|
|
4848
|
+
`dropped_below_floor=${prepared.retrieval.droppedBelowFloor}`
|
|
4849
|
+
].join(" ");
|
|
4850
|
+
const scope2 = `project=${prepared.scope.project} user=${prepared.scope.userId} session=${prepared.scope.sessionId}`;
|
|
4851
|
+
return {
|
|
4852
|
+
content: [{
|
|
4853
|
+
type: "text",
|
|
4854
|
+
text: `Found ${prepared.items.length} runtime-ranked items (${prepared.retrieval.durationMs}ms, ${scope2}, ${diagnostics}):
|
|
4498
4855
|
|
|
4499
4856
|
${prepared.context}${warnings}`
|
|
4500
|
-
|
|
4501
|
-
|
|
4857
|
+
}]
|
|
4858
|
+
};
|
|
4859
|
+
} catch (error) {
|
|
4860
|
+
const automaticWarning = `Automatic runtime unavailable: ${error.message}. Falling back to broad/manual query path.`;
|
|
4861
|
+
const queryResult2 = await queryWithDegradedFallback({
|
|
4862
|
+
project: resolvedProject,
|
|
4863
|
+
query,
|
|
4864
|
+
top_k,
|
|
4865
|
+
include_memories: include_memories === true,
|
|
4866
|
+
include_graph,
|
|
4867
|
+
user_id: user_id || resolveMcpScope({ user_id }).userId,
|
|
4868
|
+
session_id: session_id || resolveMcpScope({ session_id }).sessionId
|
|
4869
|
+
});
|
|
4870
|
+
const response2 = queryResult2.response;
|
|
4871
|
+
if (response2.results.length === 0) {
|
|
4872
|
+
return { content: [{ type: "text", text: `No relevant context found.
|
|
4873
|
+
|
|
4874
|
+
[automatic_runtime]
|
|
4875
|
+
${automaticWarning}` }] };
|
|
4876
|
+
}
|
|
4877
|
+
const scope2 = resolveMcpScope({ user_id, session_id });
|
|
4878
|
+
const header2 = `Found ${response2.meta.total} results (${response2.meta.latency_ms}ms${response2.meta.cache_hit ? ", cached" : ""}, project=${resolvedProject}, user=${scope2.userId}, session=${scope2.sessionId}):
|
|
4879
|
+
|
|
4880
|
+
`;
|
|
4881
|
+
const suffix2 = queryResult2.degraded_mode ? `
|
|
4882
|
+
|
|
4883
|
+
[degraded_mode=true] ${queryResult2.degraded_reason}
|
|
4884
|
+
Recommendation: ${queryResult2.recommendation}` : "";
|
|
4885
|
+
return { content: [{ type: "text", text: `${header2}${response2.context}
|
|
4886
|
+
|
|
4887
|
+
[automatic_runtime]
|
|
4888
|
+
${automaticWarning}${suffix2}` }] };
|
|
4889
|
+
}
|
|
4502
4890
|
}
|
|
4503
4891
|
const queryResult = await queryWithDegradedFallback({
|
|
4504
4892
|
project: resolvedProject,
|
|
@@ -4722,6 +5110,10 @@ server.tool(
|
|
|
4722
5110
|
max_duration_minutes: input.max_duration_minutes,
|
|
4723
5111
|
max_chunks: input.max_chunks
|
|
4724
5112
|
});
|
|
5113
|
+
noteAutomaticSourceActivity({
|
|
5114
|
+
project: resolvedProject,
|
|
5115
|
+
sourceIds: [result.source_id]
|
|
5116
|
+
});
|
|
4725
5117
|
return toTextResult(result);
|
|
4726
5118
|
} catch (error) {
|
|
4727
5119
|
return { content: [{ type: "text", text: `Error: ${error.message}` }] };
|
|
@@ -4810,6 +5202,11 @@ server.tool(
|
|
|
4810
5202
|
strategy_override,
|
|
4811
5203
|
profile_config
|
|
4812
5204
|
});
|
|
5205
|
+
const sourceId = result?.source_id || result?.id;
|
|
5206
|
+
noteAutomaticSourceActivity({
|
|
5207
|
+
project: resolvedProject,
|
|
5208
|
+
sourceIds: sourceId ? [String(sourceId)] : []
|
|
5209
|
+
});
|
|
4813
5210
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
4814
5211
|
}
|
|
4815
5212
|
if (!content?.trim()) {
|
|
@@ -6244,6 +6641,10 @@ server.tool(
|
|
|
6244
6641
|
max_chunks: input.max_chunks,
|
|
6245
6642
|
auto_index: true
|
|
6246
6643
|
});
|
|
6644
|
+
noteAutomaticSourceActivity({
|
|
6645
|
+
project: resolvedProject,
|
|
6646
|
+
sourceIds: [result.source_id]
|
|
6647
|
+
});
|
|
6247
6648
|
return toTextResult(result);
|
|
6248
6649
|
} catch (error) {
|
|
6249
6650
|
return { content: [{ type: "text", text: `Error: ${error.message}` }] };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usewhisper/mcp-server",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"whisperContractVersion": "2026.03.
|
|
3
|
+
"version": "2.7.0",
|
|
4
|
+
"whisperContractVersion": "2026.03.10",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "tsup ../src/mcp/server.ts --format esm --out-dir dist",
|
|
7
7
|
"prepublishOnly": "npm run build"
|