@usewhisper/mcp-server 2.5.0 → 2.8.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 +549 -104
- 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
|
}
|
|
@@ -2757,6 +3069,19 @@ var WhisperContext = class _WhisperContext {
|
|
|
2757
3069
|
}
|
|
2758
3070
|
]);
|
|
2759
3071
|
}
|
|
3072
|
+
async listMemories(params) {
|
|
3073
|
+
const projectRef = this.getRequiredProject(params.project);
|
|
3074
|
+
return this.withProjectRefFallback(projectRef, async (project) => {
|
|
3075
|
+
const query = new URLSearchParams({
|
|
3076
|
+
project,
|
|
3077
|
+
...params.user_id ? { user_id: params.user_id } : {},
|
|
3078
|
+
...params.session_id ? { session_id: params.session_id } : {},
|
|
3079
|
+
...params.agent_id ? { agent_id: params.agent_id } : {},
|
|
3080
|
+
limit: String(Math.min(Math.max(params.limit ?? 200, 1), 200))
|
|
3081
|
+
});
|
|
3082
|
+
return this.request(`/v1/memories?${query.toString()}`, { method: "GET" });
|
|
3083
|
+
});
|
|
3084
|
+
}
|
|
2760
3085
|
async addMemory(params) {
|
|
2761
3086
|
const projectRef = this.getRequiredProject(params.project);
|
|
2762
3087
|
return this.withProjectRefFallback(projectRef, async (project) => {
|
|
@@ -3421,9 +3746,10 @@ function createWhisperMcpRuntimeClient(options) {
|
|
|
3421
3746
|
}
|
|
3422
3747
|
var whisper = createWhisperMcpClient();
|
|
3423
3748
|
var runtimeClient = createWhisperMcpRuntimeClient();
|
|
3749
|
+
var runtimeSessions = /* @__PURE__ */ new Map();
|
|
3424
3750
|
var server = new McpServer({
|
|
3425
3751
|
name: "whisper-context",
|
|
3426
|
-
version: "0.
|
|
3752
|
+
version: "0.3.0"
|
|
3427
3753
|
});
|
|
3428
3754
|
function createMcpServer() {
|
|
3429
3755
|
return server;
|
|
@@ -3485,7 +3811,7 @@ function getWorkspaceIdForPath(path, workspaceId) {
|
|
|
3485
3811
|
const seed = `${path}|${DEFAULT_PROJECT || "default"}|${API_KEY.slice(0, 12)}`;
|
|
3486
3812
|
return createHash("sha256").update(seed).digest("hex").slice(0, 20);
|
|
3487
3813
|
}
|
|
3488
|
-
function
|
|
3814
|
+
function clamp012(value) {
|
|
3489
3815
|
if (Number.isNaN(value)) return 0;
|
|
3490
3816
|
if (value < 0) return 0;
|
|
3491
3817
|
if (value > 1) return 1;
|
|
@@ -3531,7 +3857,7 @@ function toEvidenceRef(source, workspaceId, methodFallback) {
|
|
|
3531
3857
|
line_start: lineStart,
|
|
3532
3858
|
...lineEnd ? { line_end: lineEnd } : {},
|
|
3533
3859
|
snippet: String(source.content || metadata.snippet || "").slice(0, 500),
|
|
3534
|
-
score:
|
|
3860
|
+
score: clamp012(Number(source.score ?? metadata.score ?? 0)),
|
|
3535
3861
|
retrieval_method: retrievalMethod,
|
|
3536
3862
|
indexed_at: String(metadata.indexed_at || (/* @__PURE__ */ new Date()).toISOString()),
|
|
3537
3863
|
...metadata.commit ? { commit: String(metadata.commit) } : {},
|
|
@@ -3606,6 +3932,21 @@ async function resolveProjectRef(explicit) {
|
|
|
3606
3932
|
return void 0;
|
|
3607
3933
|
}
|
|
3608
3934
|
}
|
|
3935
|
+
async function ingestSessionWithSyncFallback(params) {
|
|
3936
|
+
try {
|
|
3937
|
+
return await whisper.ingestSession({
|
|
3938
|
+
...params,
|
|
3939
|
+
async: false,
|
|
3940
|
+
write_mode: "sync"
|
|
3941
|
+
});
|
|
3942
|
+
} catch (error) {
|
|
3943
|
+
const message = String(error?.message || error || "").toLowerCase();
|
|
3944
|
+
if (!message.includes("sync_write_restricted")) {
|
|
3945
|
+
throw error;
|
|
3946
|
+
}
|
|
3947
|
+
return whisper.ingestSession(params);
|
|
3948
|
+
}
|
|
3949
|
+
}
|
|
3609
3950
|
function defaultMcpUserId() {
|
|
3610
3951
|
const explicit = process.env.WHISPER_USER_ID?.trim();
|
|
3611
3952
|
if (explicit) return explicit;
|
|
@@ -3625,18 +3966,55 @@ async function prepareAutomaticQuery(params) {
|
|
|
3625
3966
|
throw new Error("Whisper runtime client unavailable.");
|
|
3626
3967
|
}
|
|
3627
3968
|
const scope = resolveMcpScope(params);
|
|
3628
|
-
const
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
workspacePath
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
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
|
+
}
|
|
3636
3988
|
return runtime.beforeTurn({
|
|
3637
3989
|
userMessage: params.query
|
|
3638
3990
|
});
|
|
3639
3991
|
}
|
|
3992
|
+
function noteAutomaticSourceActivity(params) {
|
|
3993
|
+
if (!runtimeClient) return;
|
|
3994
|
+
const sourceIds = [...new Set((params.sourceIds || []).map((value) => String(value || "").trim()).filter(Boolean))];
|
|
3995
|
+
if (sourceIds.length === 0) return;
|
|
3996
|
+
const scope = resolveMcpScope(params);
|
|
3997
|
+
const key = [
|
|
3998
|
+
params.project || DEFAULT_PROJECT || "",
|
|
3999
|
+
scope.userId,
|
|
4000
|
+
scope.sessionId,
|
|
4001
|
+
scope.workspacePath || process.cwd(),
|
|
4002
|
+
String(params.top_k || 10)
|
|
4003
|
+
].join("|");
|
|
4004
|
+
let runtime = runtimeSessions.get(key);
|
|
4005
|
+
if (!runtime) {
|
|
4006
|
+
runtime = runtimeClient.createAgentRuntime({
|
|
4007
|
+
project: params.project,
|
|
4008
|
+
userId: scope.userId,
|
|
4009
|
+
sessionId: scope.sessionId,
|
|
4010
|
+
workspacePath: scope.workspacePath,
|
|
4011
|
+
topK: params.top_k,
|
|
4012
|
+
clientName: "whisper-mcp"
|
|
4013
|
+
});
|
|
4014
|
+
runtimeSessions.set(key, runtime);
|
|
4015
|
+
}
|
|
4016
|
+
runtime.noteSourceActivity(sourceIds);
|
|
4017
|
+
}
|
|
3640
4018
|
function buildAbstain(args) {
|
|
3641
4019
|
return {
|
|
3642
4020
|
status: "abstained",
|
|
@@ -4476,29 +4854,67 @@ server.tool(
|
|
|
4476
4854
|
}
|
|
4477
4855
|
const automaticMode = include_memories !== false && include_graph !== true && !(chunk_types && chunk_types.length > 0) && max_tokens === void 0 && runtimeClient;
|
|
4478
4856
|
if (automaticMode) {
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4857
|
+
try {
|
|
4858
|
+
const prepared = await prepareAutomaticQuery({
|
|
4859
|
+
project: resolvedProject,
|
|
4860
|
+
query,
|
|
4861
|
+
top_k,
|
|
4862
|
+
user_id,
|
|
4863
|
+
session_id
|
|
4864
|
+
});
|
|
4865
|
+
if (!prepared.items.length) {
|
|
4866
|
+
return { content: [{ type: "text", text: "No relevant context found." }] };
|
|
4867
|
+
}
|
|
4868
|
+
const warnings = prepared.retrieval.warnings.length ? `
|
|
4490
4869
|
|
|
4491
4870
|
[automatic_runtime]
|
|
4492
4871
|
${prepared.retrieval.warnings.join("\n")}` : "";
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4872
|
+
const diagnostics = [
|
|
4873
|
+
`focused_scope=${prepared.retrieval.focusedScopeApplied}`,
|
|
4874
|
+
`fallback_used=${prepared.retrieval.fallbackUsed}`,
|
|
4875
|
+
`deduped=${prepared.retrieval.dedupedCount}`,
|
|
4876
|
+
`dropped_below_floor=${prepared.retrieval.droppedBelowFloor}`
|
|
4877
|
+
].join(" ");
|
|
4878
|
+
const scope2 = `project=${prepared.scope.project} user=${prepared.scope.userId} session=${prepared.scope.sessionId}`;
|
|
4879
|
+
return {
|
|
4880
|
+
content: [{
|
|
4881
|
+
type: "text",
|
|
4882
|
+
text: `Found ${prepared.items.length} runtime-ranked items (${prepared.retrieval.durationMs}ms, ${scope2}, ${diagnostics}):
|
|
4498
4883
|
|
|
4499
4884
|
${prepared.context}${warnings}`
|
|
4500
|
-
|
|
4501
|
-
|
|
4885
|
+
}]
|
|
4886
|
+
};
|
|
4887
|
+
} catch (error) {
|
|
4888
|
+
const automaticWarning = `Automatic runtime unavailable: ${error.message}. Falling back to broad/manual query path.`;
|
|
4889
|
+
const queryResult2 = await queryWithDegradedFallback({
|
|
4890
|
+
project: resolvedProject,
|
|
4891
|
+
query,
|
|
4892
|
+
top_k,
|
|
4893
|
+
include_memories: include_memories === true,
|
|
4894
|
+
include_graph,
|
|
4895
|
+
user_id: user_id || resolveMcpScope({ user_id }).userId,
|
|
4896
|
+
session_id: session_id || resolveMcpScope({ session_id }).sessionId
|
|
4897
|
+
});
|
|
4898
|
+
const response2 = queryResult2.response;
|
|
4899
|
+
if (response2.results.length === 0) {
|
|
4900
|
+
return { content: [{ type: "text", text: `No relevant context found.
|
|
4901
|
+
|
|
4902
|
+
[automatic_runtime]
|
|
4903
|
+
${automaticWarning}` }] };
|
|
4904
|
+
}
|
|
4905
|
+
const scope2 = resolveMcpScope({ user_id, session_id });
|
|
4906
|
+
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}):
|
|
4907
|
+
|
|
4908
|
+
`;
|
|
4909
|
+
const suffix2 = queryResult2.degraded_mode ? `
|
|
4910
|
+
|
|
4911
|
+
[degraded_mode=true] ${queryResult2.degraded_reason}
|
|
4912
|
+
Recommendation: ${queryResult2.recommendation}` : "";
|
|
4913
|
+
return { content: [{ type: "text", text: `${header2}${response2.context}
|
|
4914
|
+
|
|
4915
|
+
[automatic_runtime]
|
|
4916
|
+
${automaticWarning}${suffix2}` }] };
|
|
4917
|
+
}
|
|
4502
4918
|
}
|
|
4503
4919
|
const queryResult = await queryWithDegradedFallback({
|
|
4504
4920
|
project: resolvedProject,
|
|
@@ -4722,6 +5138,10 @@ server.tool(
|
|
|
4722
5138
|
max_duration_minutes: input.max_duration_minutes,
|
|
4723
5139
|
max_chunks: input.max_chunks
|
|
4724
5140
|
});
|
|
5141
|
+
noteAutomaticSourceActivity({
|
|
5142
|
+
project: resolvedProject,
|
|
5143
|
+
sourceIds: [result.source_id]
|
|
5144
|
+
});
|
|
4725
5145
|
return toTextResult(result);
|
|
4726
5146
|
} catch (error) {
|
|
4727
5147
|
return { content: [{ type: "text", text: `Error: ${error.message}` }] };
|
|
@@ -4756,8 +5176,12 @@ server.tool(
|
|
|
4756
5176
|
},
|
|
4757
5177
|
async ({ project, title, content, ingestion_profile, strategy_override, profile_config }) => {
|
|
4758
5178
|
try {
|
|
5179
|
+
const resolvedProject = await resolveProjectRef(project);
|
|
5180
|
+
if (!resolvedProject) {
|
|
5181
|
+
return { content: [{ type: "text", text: "Error: No project resolved. Set WHISPER_PROJECT or provide project." }] };
|
|
5182
|
+
}
|
|
4759
5183
|
await whisper.addContext({
|
|
4760
|
-
project,
|
|
5184
|
+
project: resolvedProject,
|
|
4761
5185
|
title,
|
|
4762
5186
|
content,
|
|
4763
5187
|
metadata: {
|
|
@@ -4810,6 +5234,11 @@ server.tool(
|
|
|
4810
5234
|
strategy_override,
|
|
4811
5235
|
profile_config
|
|
4812
5236
|
});
|
|
5237
|
+
const sourceId = result?.source_id || result?.id;
|
|
5238
|
+
noteAutomaticSourceActivity({
|
|
5239
|
+
project: resolvedProject,
|
|
5240
|
+
sourceIds: sourceId ? [String(sourceId)] : []
|
|
5241
|
+
});
|
|
4813
5242
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
4814
5243
|
}
|
|
4815
5244
|
if (!content?.trim()) {
|
|
@@ -4893,7 +5322,7 @@ server.tool(
|
|
|
4893
5322
|
content: message.content,
|
|
4894
5323
|
timestamp: message.timestamp
|
|
4895
5324
|
}));
|
|
4896
|
-
const result = await
|
|
5325
|
+
const result = await ingestSessionWithSyncFallback({
|
|
4897
5326
|
project: scope.project,
|
|
4898
5327
|
session_id: scope.sessionId,
|
|
4899
5328
|
user_id: scope.userId,
|
|
@@ -5007,8 +5436,12 @@ server.tool(
|
|
|
5007
5436
|
},
|
|
5008
5437
|
async ({ project, session_id, title, expiry_days }) => {
|
|
5009
5438
|
try {
|
|
5439
|
+
const resolvedProject = await resolveProjectRef(project);
|
|
5440
|
+
if (!resolvedProject) {
|
|
5441
|
+
return { content: [{ type: "text", text: "Error: No project resolved. Set WHISPER_PROJECT or provide project." }] };
|
|
5442
|
+
}
|
|
5010
5443
|
const result = await whisper.createSharedContext({
|
|
5011
|
-
project,
|
|
5444
|
+
project: resolvedProject,
|
|
5012
5445
|
session_id,
|
|
5013
5446
|
title,
|
|
5014
5447
|
expiry_days
|
|
@@ -5263,13 +5696,11 @@ server.tool(
|
|
|
5263
5696
|
let memories = [];
|
|
5264
5697
|
if (resolvedProject) {
|
|
5265
5698
|
try {
|
|
5266
|
-
const
|
|
5699
|
+
const listed = await whisper.listMemories({
|
|
5267
5700
|
project: resolvedProject,
|
|
5268
|
-
|
|
5269
|
-
top_k: 100,
|
|
5270
|
-
include_relations: true
|
|
5701
|
+
limit: 200
|
|
5271
5702
|
});
|
|
5272
|
-
memories =
|
|
5703
|
+
memories = Array.isArray(listed?.memories) ? listed.memories : [];
|
|
5273
5704
|
} catch {
|
|
5274
5705
|
memories = [];
|
|
5275
5706
|
}
|
|
@@ -5360,7 +5791,7 @@ server.tool(
|
|
|
5360
5791
|
session_summaries: 0,
|
|
5361
5792
|
documents: 0
|
|
5362
5793
|
};
|
|
5363
|
-
const resolvedProject = await resolveProjectRef(bundle.project);
|
|
5794
|
+
const resolvedProject = await resolveProjectRef(bundle.project || cachedProjectRef || DEFAULT_PROJECT || void 0);
|
|
5364
5795
|
async function shouldSkipImportedMemory(memory) {
|
|
5365
5796
|
if (dedupe_strategy === "none" || !resolvedProject) return false;
|
|
5366
5797
|
const exactId = normalizeString2(memory?.id);
|
|
@@ -6244,6 +6675,10 @@ server.tool(
|
|
|
6244
6675
|
max_chunks: input.max_chunks,
|
|
6245
6676
|
auto_index: true
|
|
6246
6677
|
});
|
|
6678
|
+
noteAutomaticSourceActivity({
|
|
6679
|
+
project: resolvedProject,
|
|
6680
|
+
sourceIds: [result.source_id]
|
|
6681
|
+
});
|
|
6247
6682
|
return toTextResult(result);
|
|
6248
6683
|
} catch (error) {
|
|
6249
6684
|
return { content: [{ type: "text", text: `Error: ${error.message}` }] };
|
|
@@ -6310,7 +6745,13 @@ server.tool(
|
|
|
6310
6745
|
content,
|
|
6311
6746
|
timestamp
|
|
6312
6747
|
});
|
|
6313
|
-
const
|
|
6748
|
+
const resolvedProject = await resolveProjectRef(project);
|
|
6749
|
+
const result = await ingestSessionWithSyncFallback({
|
|
6750
|
+
project: resolvedProject,
|
|
6751
|
+
session_id,
|
|
6752
|
+
user_id,
|
|
6753
|
+
messages: normalizedMessages
|
|
6754
|
+
});
|
|
6314
6755
|
return primaryToolSuccess({
|
|
6315
6756
|
tool: "record",
|
|
6316
6757
|
session_id,
|
|
@@ -6368,7 +6809,11 @@ server.tool(
|
|
|
6368
6809
|
},
|
|
6369
6810
|
async ({ project, session_id, title, expiry_days }) => {
|
|
6370
6811
|
try {
|
|
6371
|
-
const
|
|
6812
|
+
const resolvedProject = await resolveProjectRef(project);
|
|
6813
|
+
if (!resolvedProject) {
|
|
6814
|
+
return primaryToolError("No project resolved. Set WHISPER_PROJECT or provide project.");
|
|
6815
|
+
}
|
|
6816
|
+
const result = await whisper.createSharedContext({ project: resolvedProject, session_id, title, expiry_days });
|
|
6372
6817
|
return primaryToolSuccess({
|
|
6373
6818
|
tool: "share_context",
|
|
6374
6819
|
share_id: result.share_id,
|
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.8.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"
|