@usewhisper/sdk 3.4.0 → 3.5.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.
Files changed (5) hide show
  1. package/index.d.mts +242 -1
  2. package/index.d.ts +242 -1
  3. package/index.js +755 -11
  4. package/index.mjs +755 -11
  5. package/package.json +1 -1
package/index.mjs CHANGED
@@ -1550,7 +1550,582 @@ var AnalyticsModule = class {
1550
1550
  }
1551
1551
  };
1552
1552
 
1553
+ // ../src/sdk/agent-runtime.ts
1554
+ function detectBrowserStorage() {
1555
+ const maybeStorage = globalThis.localStorage;
1556
+ if (!maybeStorage || typeof maybeStorage !== "object") return null;
1557
+ const candidate = maybeStorage;
1558
+ if (typeof candidate.getItem !== "function" || typeof candidate.setItem !== "function") {
1559
+ return null;
1560
+ }
1561
+ return {
1562
+ getItem: candidate.getItem,
1563
+ setItem: candidate.setItem
1564
+ };
1565
+ }
1566
+ function createBindingStore(filePath) {
1567
+ const storage = detectBrowserStorage();
1568
+ if (storage) {
1569
+ const key = "whisper_agent_runtime_bindings";
1570
+ return {
1571
+ async load() {
1572
+ const raw = storage.getItem(key);
1573
+ if (!raw) return {};
1574
+ try {
1575
+ const parsed = JSON.parse(raw);
1576
+ return parsed && typeof parsed === "object" ? parsed : {};
1577
+ } catch {
1578
+ return {};
1579
+ }
1580
+ },
1581
+ async save(bindings) {
1582
+ storage.setItem(key, JSON.stringify(bindings));
1583
+ }
1584
+ };
1585
+ }
1586
+ return {
1587
+ async load() {
1588
+ if (typeof process === "undefined") return {};
1589
+ const fs = await import("fs/promises");
1590
+ const path = filePath || `${process.env.USERPROFILE || process.env.HOME || "."}/.whisper/sdk/agent-bindings.json`;
1591
+ try {
1592
+ const raw = await fs.readFile(path, "utf8");
1593
+ const parsed = JSON.parse(raw);
1594
+ return parsed && typeof parsed === "object" ? parsed : {};
1595
+ } catch {
1596
+ return {};
1597
+ }
1598
+ },
1599
+ async save(bindings) {
1600
+ if (typeof process === "undefined") return;
1601
+ const fs = await import("fs/promises");
1602
+ const pathMod = await import("path");
1603
+ const path = filePath || `${process.env.USERPROFILE || process.env.HOME || "."}/.whisper/sdk/agent-bindings.json`;
1604
+ await fs.mkdir(pathMod.dirname(path), { recursive: true });
1605
+ await fs.writeFile(path, JSON.stringify(bindings), "utf8");
1606
+ }
1607
+ };
1608
+ }
1609
+ function normalizeWorkspacePath(value) {
1610
+ const trimmed = value?.trim();
1611
+ if (!trimmed) return null;
1612
+ return trimmed.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
1613
+ }
1614
+ function pathBase(value) {
1615
+ const normalized = value.replace(/\\/g, "/");
1616
+ const parts = normalized.split("/").filter(Boolean);
1617
+ return parts[parts.length - 1] || normalized;
1618
+ }
1619
+ function defaultSalience(kind, success) {
1620
+ if (kind === "decision" || kind === "constraint" || kind === "failure") return "high";
1621
+ if (kind === "outcome" || kind === "task_update") return "medium";
1622
+ if (kind === "tool_result" && success === false) return "high";
1623
+ return "low";
1624
+ }
1625
+ function toMemoryType(kind) {
1626
+ if (kind === "decision" || kind === "constraint") return "instruction";
1627
+ return "event";
1628
+ }
1629
+ function summarizeLowSalience(events) {
1630
+ const lines = events.slice(-10).map((event) => {
1631
+ const fileSuffix = event.filePaths?.length ? ` [files: ${event.filePaths.join(", ")}]` : "";
1632
+ const toolSuffix = event.toolName ? ` [tool: ${event.toolName}]` : "";
1633
+ return `- ${event.kind}: ${event.summary}${fileSuffix}${toolSuffix}`;
1634
+ });
1635
+ return `Recent low-salience work:
1636
+ ${lines.join("\n")}`;
1637
+ }
1638
+ function compactWhitespace(value) {
1639
+ return value.replace(/\s+/g, " ").trim();
1640
+ }
1641
+ function withTimeout(promise, timeoutMs) {
1642
+ return new Promise((resolve, reject) => {
1643
+ const timeout = setTimeout(() => {
1644
+ reject(new Error("timeout"));
1645
+ }, timeoutMs);
1646
+ promise.then(
1647
+ (value) => {
1648
+ clearTimeout(timeout);
1649
+ resolve(value);
1650
+ },
1651
+ (error) => {
1652
+ clearTimeout(timeout);
1653
+ reject(error);
1654
+ }
1655
+ );
1656
+ });
1657
+ }
1658
+ function extractTimestamp(metadata) {
1659
+ const candidates = [
1660
+ metadata?.updatedAt,
1661
+ metadata?.createdAt,
1662
+ metadata?.timestamp,
1663
+ metadata?.event_date,
1664
+ metadata?.eventDate
1665
+ ];
1666
+ for (const value of candidates) {
1667
+ if (typeof value === "string") {
1668
+ const parsed = Date.parse(value);
1669
+ if (!Number.isNaN(parsed)) return parsed;
1670
+ }
1671
+ }
1672
+ return 0;
1673
+ }
1674
+ function salienceBoost(metadata) {
1675
+ const value = metadata?.salience;
1676
+ if (value === "high") return 0.12;
1677
+ if (value === "medium") return 0.06;
1678
+ return 0;
1679
+ }
1680
+ var WhisperAgentRuntime = class {
1681
+ constructor(args) {
1682
+ this.args = args;
1683
+ this.bindingStore = createBindingStore(args.options.bindingStorePath);
1684
+ this.topK = args.options.topK ?? 6;
1685
+ this.maxTokens = args.options.maxTokens ?? 4e3;
1686
+ this.targetRetrievalMs = args.options.targetRetrievalMs ?? 2500;
1687
+ this.hardRetrievalTimeoutMs = args.options.hardRetrievalTimeoutMs ?? 4e3;
1688
+ this.recentWorkLimit = args.options.recentWorkLimit ?? 40;
1689
+ this.baseContext = args.baseContext;
1690
+ this.clientName = args.baseContext.clientName || "whisper-agent-runtime";
1691
+ }
1692
+ bindingStore;
1693
+ topK;
1694
+ maxTokens;
1695
+ targetRetrievalMs;
1696
+ hardRetrievalTimeoutMs;
1697
+ recentWorkLimit;
1698
+ baseContext;
1699
+ clientName;
1700
+ bindings = null;
1701
+ touchedFiles = [];
1702
+ recentWork = [];
1703
+ bufferedLowSalience = [];
1704
+ lastPreparedTurn = null;
1705
+ mergedCount = 0;
1706
+ droppedCount = 0;
1707
+ lastScope = {};
1708
+ async getBindings() {
1709
+ if (!this.bindings) {
1710
+ this.bindings = await this.bindingStore.load();
1711
+ }
1712
+ return this.bindings;
1713
+ }
1714
+ pushTouchedFiles(paths) {
1715
+ if (!paths || paths.length === 0) return;
1716
+ for (const path of paths) {
1717
+ if (!path) continue;
1718
+ this.touchedFiles = [...this.touchedFiles.filter((entry) => entry !== path), path].slice(-20);
1719
+ }
1720
+ }
1721
+ pushWorkEvent(event) {
1722
+ this.recentWork = [...this.recentWork, event].slice(-this.recentWorkLimit);
1723
+ }
1724
+ makeTaskFrameQuery(input) {
1725
+ const task = compactWhitespace(input.taskSummary || "");
1726
+ const salient = this.recentWork.filter((event) => event.salience === "high").slice(-3).map((event) => `${event.kind}: ${event.summary}`);
1727
+ const files = [...input.touchedFiles || [], ...this.touchedFiles].slice(-3).map((file) => pathBase(file));
1728
+ const parts = [
1729
+ task ? `task ${task}` : "",
1730
+ salient.length > 0 ? `recent ${salient.join(" ; ")}` : "",
1731
+ files.length > 0 ? `files ${files.join(" ")}` : "",
1732
+ input.toolContext ? `tool context ${compactWhitespace(input.toolContext)}` : ""
1733
+ ].filter(Boolean);
1734
+ if (parts.length === 0) return null;
1735
+ return parts.join(" | ");
1736
+ }
1737
+ async resolveScope(overrides) {
1738
+ const merged = {
1739
+ ...this.baseContext,
1740
+ ...overrides
1741
+ };
1742
+ const normalizedWorkspace = normalizeWorkspacePath(merged.workspacePath);
1743
+ const bindings = await this.getBindings();
1744
+ const workspaceProject = normalizedWorkspace ? bindings[normalizedWorkspace] : void 0;
1745
+ const configuredProject = merged.project;
1746
+ let projectRef = configuredProject;
1747
+ let projectSource = overrides?.project ? "explicit" : "generated";
1748
+ let warning;
1749
+ if (workspaceProject) {
1750
+ projectRef = workspaceProject;
1751
+ projectSource = "workspace";
1752
+ if (configuredProject && workspaceProject !== configuredProject) {
1753
+ warning = `workspace mapping '${workspaceProject}' overrides configured project '${configuredProject}'`;
1754
+ }
1755
+ } else if (configuredProject) {
1756
+ projectRef = configuredProject;
1757
+ projectSource = overrides?.project ? "explicit" : "config";
1758
+ }
1759
+ const project = (await this.args.adapter.resolveProject(projectRef)).id;
1760
+ if (normalizedWorkspace) {
1761
+ bindings[normalizedWorkspace] = project;
1762
+ await this.bindingStore.save(bindings);
1763
+ }
1764
+ const scope = {
1765
+ ...merged,
1766
+ project,
1767
+ userId: merged.userId || `${this.clientName}-user`,
1768
+ sessionId: merged.sessionId || `sess_${stableHash(`${this.clientName}_${normalizedWorkspace || "default"}`)}`
1769
+ };
1770
+ this.lastScope = {
1771
+ project: scope.project,
1772
+ userId: scope.userId,
1773
+ sessionId: scope.sessionId,
1774
+ source: projectSource,
1775
+ warning
1776
+ };
1777
+ return { scope, projectSource, warning };
1778
+ }
1779
+ async runBranch(name, task) {
1780
+ const startedAt = Date.now();
1781
+ try {
1782
+ const value = await withTimeout(task(), this.hardRetrievalTimeoutMs);
1783
+ return {
1784
+ name,
1785
+ status: "ok",
1786
+ durationMs: Date.now() - startedAt,
1787
+ value
1788
+ };
1789
+ } catch (error) {
1790
+ const durationMs = Date.now() - startedAt;
1791
+ const reason = error instanceof Error ? error.message : String(error);
1792
+ return {
1793
+ name,
1794
+ status: reason === "timeout" ? "timeout" : "error",
1795
+ durationMs,
1796
+ reason
1797
+ };
1798
+ }
1799
+ }
1800
+ contextItems(result, sourceQuery) {
1801
+ return (result.results || []).map((item) => ({
1802
+ id: item.id,
1803
+ content: item.content,
1804
+ type: "project",
1805
+ score: item.score ?? 0,
1806
+ sourceQuery,
1807
+ metadata: item.metadata || {}
1808
+ }));
1809
+ }
1810
+ memoryItems(result, sourceQuery) {
1811
+ return (result.results || []).map((item, index) => ({
1812
+ id: item.memory?.id || item.chunk?.id || `${sourceQuery}_memory_${index}`,
1813
+ content: item.chunk?.content || item.memory?.content || "",
1814
+ type: "memory",
1815
+ score: item.similarity ?? 0,
1816
+ sourceQuery,
1817
+ metadata: {
1818
+ ...item.chunk?.metadata || {},
1819
+ ...item.memory?.temporal || {},
1820
+ confidence: item.memory?.confidence
1821
+ }
1822
+ })).filter((item) => item.content);
1823
+ }
1824
+ rerank(items) {
1825
+ const deduped = /* @__PURE__ */ new Map();
1826
+ for (const item of items) {
1827
+ const key = `${item.id}:${item.content.toLowerCase()}`;
1828
+ const recency = extractTimestamp(item.metadata) > 0 ? 0.04 : 0;
1829
+ const queryBonus = item.sourceQuery === "primary" ? 0.08 : item.sourceQuery === "task_frame" ? 0.04 : 0.03;
1830
+ const next = {
1831
+ ...item,
1832
+ score: item.score + queryBonus + salienceBoost(item.metadata) + recency
1833
+ };
1834
+ const existing = deduped.get(key);
1835
+ if (!existing || next.score > existing.score) {
1836
+ deduped.set(key, next);
1837
+ }
1838
+ }
1839
+ return [...deduped.values()].sort((left, right) => right.score - left.score);
1840
+ }
1841
+ buildContext(items) {
1842
+ const maxChars = this.maxTokens * 4;
1843
+ const lines = [];
1844
+ let used = 0;
1845
+ for (const item of items) {
1846
+ const label = item.type === "memory" ? "memory" : "context";
1847
+ const content = compactWhitespace(item.content);
1848
+ if (!content) continue;
1849
+ const line = `[${label}] ${content}`;
1850
+ if (used + line.length > maxChars) break;
1851
+ lines.push(line);
1852
+ used += line.length;
1853
+ }
1854
+ if (lines.length === 0) return "";
1855
+ return `Relevant context:
1856
+ ${lines.join("\n")}`;
1857
+ }
1858
+ async bootstrap(context = {}) {
1859
+ const { scope, warning } = await this.resolveScope(context);
1860
+ const warnings = warning ? [warning] : [];
1861
+ const startedAt = Date.now();
1862
+ const branches = await Promise.all([
1863
+ this.runBranch("session_recent", () => this.args.adapter.getSessionMemories({
1864
+ project: scope.project,
1865
+ session_id: scope.sessionId,
1866
+ include_pending: true,
1867
+ limit: 12
1868
+ })),
1869
+ this.runBranch("user_profile", () => scope.userId ? this.args.adapter.getUserProfile({
1870
+ project: scope.project,
1871
+ user_id: scope.userId,
1872
+ include_pending: true,
1873
+ memory_types: "preference,instruction,goal"
1874
+ }) : Promise.resolve({ user_id: scope.userId, memories: [], count: 0 })),
1875
+ this.runBranch("project_rules", () => this.args.adapter.query({
1876
+ project: scope.project,
1877
+ query: "project rules instructions constraints conventions open threads",
1878
+ top_k: this.topK,
1879
+ include_memories: false,
1880
+ user_id: scope.userId,
1881
+ session_id: scope.sessionId,
1882
+ max_tokens: this.maxTokens,
1883
+ compress: true,
1884
+ compression_strategy: "adaptive"
1885
+ }))
1886
+ ]);
1887
+ const items = [];
1888
+ const branchStatus = {};
1889
+ for (const branch of branches) {
1890
+ branchStatus[branch.name] = branch.status;
1891
+ if (branch.status !== "ok") {
1892
+ if (branch.reason) warnings.push(`${branch.name}:${branch.reason}`);
1893
+ continue;
1894
+ }
1895
+ if (branch.name === "project_rules") {
1896
+ items.push(...this.contextItems(branch.value, "bootstrap"));
1897
+ continue;
1898
+ }
1899
+ const records = branch.value.memories || [];
1900
+ items.push(...records.map((memory, index) => ({
1901
+ id: String(memory.id || `${branch.name}_${index}`),
1902
+ content: String(memory.content || ""),
1903
+ type: "memory",
1904
+ score: 0.4,
1905
+ sourceQuery: "bootstrap",
1906
+ metadata: memory
1907
+ })).filter((item) => item.content));
1908
+ }
1909
+ const ranked = this.rerank(items).slice(0, this.topK * 2);
1910
+ const prepared = {
1911
+ scope,
1912
+ retrieval: {
1913
+ primaryQuery: "bootstrap",
1914
+ taskFrameQuery: null,
1915
+ warnings,
1916
+ degraded: warnings.length > 0,
1917
+ degradedReason: warnings.length > 0 ? "partial_bootstrap" : void 0,
1918
+ durationMs: Date.now() - startedAt,
1919
+ targetBudgetMs: this.targetRetrievalMs,
1920
+ hardTimeoutMs: this.hardRetrievalTimeoutMs,
1921
+ branchStatus
1922
+ },
1923
+ context: this.buildContext(ranked),
1924
+ items: ranked
1925
+ };
1926
+ this.lastPreparedTurn = prepared.retrieval;
1927
+ return prepared;
1928
+ }
1929
+ async beforeTurn(input, context = {}) {
1930
+ this.pushTouchedFiles(input.touchedFiles);
1931
+ const { scope, warning } = await this.resolveScope(context);
1932
+ const primaryQuery = compactWhitespace(input.userMessage);
1933
+ const taskFrameQuery = this.makeTaskFrameQuery(input);
1934
+ const warnings = warning ? [warning] : [];
1935
+ const startedAt = Date.now();
1936
+ const branches = await Promise.all([
1937
+ this.runBranch("context_primary", () => this.args.adapter.query({
1938
+ project: scope.project,
1939
+ query: primaryQuery,
1940
+ top_k: this.topK,
1941
+ include_memories: false,
1942
+ user_id: scope.userId,
1943
+ session_id: scope.sessionId,
1944
+ max_tokens: this.maxTokens,
1945
+ compress: true,
1946
+ compression_strategy: "adaptive"
1947
+ })),
1948
+ this.runBranch("memory_primary", () => this.args.adapter.searchMemories({
1949
+ project: scope.project,
1950
+ query: primaryQuery,
1951
+ user_id: scope.userId,
1952
+ session_id: scope.sessionId,
1953
+ top_k: this.topK,
1954
+ include_pending: true,
1955
+ profile: "balanced"
1956
+ })),
1957
+ taskFrameQuery ? this.runBranch("context_task_frame", () => this.args.adapter.query({
1958
+ project: scope.project,
1959
+ query: taskFrameQuery,
1960
+ top_k: this.topK,
1961
+ include_memories: false,
1962
+ user_id: scope.userId,
1963
+ session_id: scope.sessionId,
1964
+ max_tokens: this.maxTokens,
1965
+ compress: true,
1966
+ compression_strategy: "adaptive"
1967
+ })) : Promise.resolve({
1968
+ name: "context_task_frame",
1969
+ status: "skipped",
1970
+ durationMs: 0
1971
+ }),
1972
+ taskFrameQuery ? this.runBranch("memory_task_frame", () => this.args.adapter.searchMemories({
1973
+ project: scope.project,
1974
+ query: taskFrameQuery,
1975
+ user_id: scope.userId,
1976
+ session_id: scope.sessionId,
1977
+ top_k: this.topK,
1978
+ include_pending: true,
1979
+ profile: "balanced"
1980
+ })) : Promise.resolve({
1981
+ name: "memory_task_frame",
1982
+ status: "skipped",
1983
+ durationMs: 0
1984
+ })
1985
+ ]);
1986
+ const branchStatus = {};
1987
+ const collected = [];
1988
+ let okCount = 0;
1989
+ for (const branch of branches) {
1990
+ branchStatus[branch.name] = branch.status;
1991
+ if (branch.status !== "ok") {
1992
+ if (branch.status !== "skipped" && branch.reason) warnings.push(`${branch.name}:${branch.reason}`);
1993
+ continue;
1994
+ }
1995
+ okCount += 1;
1996
+ if (branch.name.startsWith("context")) {
1997
+ collected.push(...this.contextItems(
1998
+ branch.value,
1999
+ branch.name.includes("task_frame") ? "task_frame" : "primary"
2000
+ ));
2001
+ } else {
2002
+ collected.push(...this.memoryItems(
2003
+ branch.value,
2004
+ branch.name.includes("task_frame") ? "task_frame" : "primary"
2005
+ ));
2006
+ }
2007
+ }
2008
+ const ranked = this.rerank(collected).slice(0, this.topK * 2);
2009
+ const prepared = {
2010
+ scope,
2011
+ retrieval: {
2012
+ primaryQuery,
2013
+ taskFrameQuery,
2014
+ warnings,
2015
+ degraded: okCount < branches.filter((branch) => branch.status !== "skipped").length,
2016
+ degradedReason: okCount === 0 ? "all_retrieval_failed" : warnings.length > 0 ? "partial_retrieval_failed" : void 0,
2017
+ durationMs: Date.now() - startedAt,
2018
+ targetBudgetMs: this.targetRetrievalMs,
2019
+ hardTimeoutMs: this.hardRetrievalTimeoutMs,
2020
+ branchStatus
2021
+ },
2022
+ context: this.buildContext(ranked),
2023
+ items: ranked
2024
+ };
2025
+ this.lastPreparedTurn = prepared.retrieval;
2026
+ return prepared;
2027
+ }
2028
+ async recordWork(event, context = {}) {
2029
+ const normalized = {
2030
+ ...event,
2031
+ salience: event.salience || defaultSalience(event.kind, event.success),
2032
+ timestamp: event.timestamp || nowIso()
2033
+ };
2034
+ this.pushTouchedFiles(normalized.filePaths);
2035
+ this.pushWorkEvent(normalized);
2036
+ if (normalized.salience === "low") {
2037
+ this.bufferedLowSalience = [...this.bufferedLowSalience, normalized].slice(-20);
2038
+ return { success: true, buffered: true };
2039
+ }
2040
+ const { scope } = await this.resolveScope(context);
2041
+ return this.args.adapter.addMemory({
2042
+ project: scope.project,
2043
+ user_id: scope.userId,
2044
+ session_id: scope.sessionId,
2045
+ content: `${normalized.kind}: ${normalized.summary}${normalized.details ? ` (${normalized.details})` : ""}`,
2046
+ memory_type: toMemoryType(normalized.kind),
2047
+ event_date: normalized.timestamp,
2048
+ write_mode: "async",
2049
+ metadata: {
2050
+ runtime_auto: true,
2051
+ client_name: this.clientName,
2052
+ work_event_kind: normalized.kind,
2053
+ salience: normalized.salience,
2054
+ file_paths: normalized.filePaths || [],
2055
+ tool_name: normalized.toolName,
2056
+ success: normalized.success
2057
+ }
2058
+ });
2059
+ }
2060
+ async afterTurn(input, context = {}) {
2061
+ this.pushTouchedFiles(input.touchedFiles);
2062
+ const { scope } = await this.resolveScope(context);
2063
+ const result = await this.args.adapter.ingestSession({
2064
+ project: scope.project,
2065
+ session_id: scope.sessionId,
2066
+ user_id: scope.userId,
2067
+ messages: [
2068
+ { role: "user", content: input.userMessage, timestamp: nowIso() },
2069
+ { role: "assistant", content: input.assistantMessage, timestamp: nowIso() }
2070
+ ],
2071
+ write_mode: "async"
2072
+ });
2073
+ this.mergedCount += result.memories_invalidated || 0;
2074
+ return {
2075
+ success: Boolean(result.success),
2076
+ sessionIngested: true,
2077
+ memoriesCreated: result.memories_created || 0,
2078
+ relationsCreated: result.relations_created || 0,
2079
+ invalidatedCount: result.memories_invalidated || 0,
2080
+ mergedCount: result.memories_invalidated || 0,
2081
+ droppedCount: 0,
2082
+ warnings: result.errors || []
2083
+ };
2084
+ }
2085
+ async flush(reason = "manual", context = {}) {
2086
+ if (this.bufferedLowSalience.length > 0) {
2087
+ const { scope } = await this.resolveScope(context);
2088
+ const summary = summarizeLowSalience(this.bufferedLowSalience);
2089
+ await this.args.adapter.addMemory({
2090
+ project: scope.project,
2091
+ user_id: scope.userId,
2092
+ session_id: scope.sessionId,
2093
+ content: summary,
2094
+ memory_type: "event",
2095
+ write_mode: "async",
2096
+ metadata: {
2097
+ runtime_auto: true,
2098
+ client_name: this.clientName,
2099
+ flush_reason: reason,
2100
+ salience: "low",
2101
+ summarized_count: this.bufferedLowSalience.length
2102
+ }
2103
+ });
2104
+ this.bufferedLowSalience = [];
2105
+ }
2106
+ await this.args.adapter.flushQueue();
2107
+ return this.status();
2108
+ }
2109
+ status() {
2110
+ return {
2111
+ clientName: this.clientName,
2112
+ scope: this.lastScope,
2113
+ queue: this.args.adapter.queueStatus(),
2114
+ retrieval: this.lastPreparedTurn,
2115
+ counters: {
2116
+ mergedCount: this.mergedCount,
2117
+ droppedCount: this.droppedCount,
2118
+ bufferedLowSalience: this.bufferedLowSalience.length
2119
+ }
2120
+ };
2121
+ }
2122
+ };
2123
+
1553
2124
  // ../src/sdk/whisper.ts
2125
+ var PROJECT_CACHE_TTL_MS = 3e4;
2126
+ function isLikelyProjectId(projectRef) {
2127
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(projectRef);
2128
+ }
1554
2129
  var WhisperClient = class _WhisperClient {
1555
2130
  constructor(config) {
1556
2131
  this.config = config;
@@ -1570,7 +2145,7 @@ var WhisperClient = class _WhisperClient {
1570
2145
  config.cache?.ttlMs ?? 7e3,
1571
2146
  config.cache?.capacity ?? 500
1572
2147
  );
1573
- const queueStore = config.queue?.persistence === "storage" ? createStorageQueueStore() : config.queue?.persistence === "file" && config.queue.filePath ? createFileQueueStore(config.queue.filePath) : new InMemoryQueueStore();
2148
+ const queueStore = this.createQueueStore(config);
1574
2149
  this.writeQueue = new WriteQueue({
1575
2150
  store: queueStore,
1576
2151
  maxBatchSize: config.queue?.maxBatchSize ?? 50,
@@ -1703,6 +2278,9 @@ var WhisperClient = class _WhisperClient {
1703
2278
  sessionModule;
1704
2279
  profileModule;
1705
2280
  analyticsModule;
2281
+ projectRefToId = /* @__PURE__ */ new Map();
2282
+ projectCache = [];
2283
+ projectCacheExpiresAt = 0;
1706
2284
  static fromEnv(overrides = {}) {
1707
2285
  const env = typeof process !== "undefined" ? process.env : {};
1708
2286
  const apiKey = overrides.apiKey || env.WHISPER_API_KEY || env.USEWHISPER_API_KEY || env.API_KEY;
@@ -1716,6 +2294,172 @@ var WhisperClient = class _WhisperClient {
1716
2294
  ...overrides
1717
2295
  });
1718
2296
  }
2297
+ createQueueStore(config) {
2298
+ const persistence = config.queue?.persistence || this.defaultQueuePersistence();
2299
+ if (persistence === "storage") {
2300
+ return createStorageQueueStore();
2301
+ }
2302
+ if (persistence === "file") {
2303
+ const filePath = config.queue?.filePath || this.defaultQueueFilePath();
2304
+ if (filePath) {
2305
+ return createFileQueueStore(filePath);
2306
+ }
2307
+ }
2308
+ return new InMemoryQueueStore();
2309
+ }
2310
+ defaultQueuePersistence() {
2311
+ const maybeWindow = globalThis.window;
2312
+ if (maybeWindow && typeof maybeWindow === "object") {
2313
+ const maybeStorage = globalThis.localStorage;
2314
+ return maybeStorage && typeof maybeStorage === "object" ? "storage" : "memory";
2315
+ }
2316
+ return "file";
2317
+ }
2318
+ defaultQueueFilePath() {
2319
+ if (typeof process === "undefined") return void 0;
2320
+ const path = process.env.WHISPER_QUEUE_FILE_PATH;
2321
+ if (path) return path;
2322
+ const home = process.env.USERPROFILE || process.env.HOME;
2323
+ if (!home) return void 0;
2324
+ const normalizedHome = home.replace(/[\\\/]+$/, "");
2325
+ return `${normalizedHome}/.whisper/sdk/queue.json`;
2326
+ }
2327
+ getRequiredProject(project) {
2328
+ const resolved = project || this.config.project;
2329
+ if (!resolved) {
2330
+ throw new RuntimeClientError({
2331
+ code: "MISSING_PROJECT",
2332
+ message: "Project is required",
2333
+ retryable: false
2334
+ });
2335
+ }
2336
+ return resolved;
2337
+ }
2338
+ async refreshProjectCache(force = false) {
2339
+ if (!force && Date.now() < this.projectCacheExpiresAt && this.projectCache.length > 0) {
2340
+ return this.projectCache;
2341
+ }
2342
+ const response = await this.runtimeClient.request({
2343
+ endpoint: "/v1/projects",
2344
+ method: "GET",
2345
+ operation: "get",
2346
+ idempotent: true
2347
+ });
2348
+ this.projectRefToId.clear();
2349
+ this.projectCache = response.data?.projects || [];
2350
+ for (const project of this.projectCache) {
2351
+ this.projectRefToId.set(project.id, project.id);
2352
+ this.projectRefToId.set(project.slug, project.id);
2353
+ this.projectRefToId.set(project.name, project.id);
2354
+ }
2355
+ this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
2356
+ return this.projectCache;
2357
+ }
2358
+ async fetchResolvedProject(projectRef) {
2359
+ try {
2360
+ const response = await this.runtimeClient.request({
2361
+ endpoint: `/v1/projects/resolve?project=${encodeURIComponent(projectRef)}`,
2362
+ method: "GET",
2363
+ operation: "get",
2364
+ idempotent: true
2365
+ });
2366
+ return response.data?.resolved || null;
2367
+ } catch (error) {
2368
+ if (error instanceof RuntimeClientError && error.status === 404) {
2369
+ return null;
2370
+ }
2371
+ throw error;
2372
+ }
2373
+ }
2374
+ async resolveProject(projectRef) {
2375
+ const resolvedRef = this.getRequiredProject(projectRef);
2376
+ const cachedProjects = await this.refreshProjectCache(false);
2377
+ const cachedProject = cachedProjects.find(
2378
+ (project) => project.id === resolvedRef || project.slug === resolvedRef || project.name === resolvedRef
2379
+ );
2380
+ if (cachedProject) {
2381
+ return cachedProject;
2382
+ }
2383
+ const resolvedProject = await this.fetchResolvedProject(resolvedRef);
2384
+ if (resolvedProject) {
2385
+ this.projectRefToId.set(resolvedProject.id, resolvedProject.id);
2386
+ this.projectRefToId.set(resolvedProject.slug, resolvedProject.id);
2387
+ this.projectRefToId.set(resolvedProject.name, resolvedProject.id);
2388
+ this.projectCache = [
2389
+ ...this.projectCache.filter((project) => project.id !== resolvedProject.id),
2390
+ resolvedProject
2391
+ ];
2392
+ this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
2393
+ return resolvedProject;
2394
+ }
2395
+ if (isLikelyProjectId(resolvedRef)) {
2396
+ return {
2397
+ id: resolvedRef,
2398
+ orgId: "",
2399
+ name: resolvedRef,
2400
+ slug: resolvedRef,
2401
+ createdAt: (/* @__PURE__ */ new Date(0)).toISOString(),
2402
+ updatedAt: (/* @__PURE__ */ new Date(0)).toISOString()
2403
+ };
2404
+ }
2405
+ throw new RuntimeClientError({
2406
+ code: "PROJECT_NOT_FOUND",
2407
+ message: `Project '${resolvedRef}' not found`,
2408
+ retryable: false
2409
+ });
2410
+ }
2411
+ async query(params) {
2412
+ const project = (await this.resolveProject(params.project)).id;
2413
+ const response = await this.runtimeClient.request({
2414
+ endpoint: "/v1/context/query",
2415
+ method: "POST",
2416
+ operation: "search",
2417
+ body: {
2418
+ ...params,
2419
+ project
2420
+ },
2421
+ idempotent: true
2422
+ });
2423
+ return response.data;
2424
+ }
2425
+ async ingestSession(params) {
2426
+ const project = (await this.resolveProject(params.project)).id;
2427
+ const response = await this.runtimeClient.request({
2428
+ endpoint: "/v1/memory/ingest/session",
2429
+ method: "POST",
2430
+ operation: "session",
2431
+ body: {
2432
+ ...params,
2433
+ project
2434
+ }
2435
+ });
2436
+ return response.data;
2437
+ }
2438
+ createAgentRuntime(options = {}) {
2439
+ const baseContext = {
2440
+ workspacePath: options.workspacePath,
2441
+ project: options.project || this.config.project,
2442
+ userId: options.userId,
2443
+ sessionId: options.sessionId,
2444
+ traceId: options.traceId,
2445
+ clientName: options.clientName
2446
+ };
2447
+ return new WhisperAgentRuntime({
2448
+ baseContext,
2449
+ options,
2450
+ adapter: {
2451
+ resolveProject: (project) => this.resolveProject(project),
2452
+ query: (params) => this.query(params),
2453
+ ingestSession: (params) => this.ingestSession(params),
2454
+ getSessionMemories: (params) => this.memory.getSessionMemories(params),
2455
+ getUserProfile: (params) => this.memory.getUserProfile(params),
2456
+ searchMemories: (params) => this.memory.search(params),
2457
+ addMemory: (params) => this.memory.add(params),
2458
+ queueStatus: () => this.queue.status(),
2459
+ flushQueue: () => this.queue.flush()
2460
+ }
2461
+ });
2462
+ }
1719
2463
  withRunContext(context) {
1720
2464
  const base = this;
1721
2465
  return {
@@ -2257,7 +3001,7 @@ var DEFAULT_MAX_ATTEMPTS = 3;
2257
3001
  var DEFAULT_BASE_DELAY_MS = 250;
2258
3002
  var DEFAULT_MAX_DELAY_MS = 2e3;
2259
3003
  var DEFAULT_TIMEOUT_MS = 15e3;
2260
- var PROJECT_CACHE_TTL_MS = 3e4;
3004
+ var PROJECT_CACHE_TTL_MS2 = 3e4;
2261
3005
  var DEPRECATION_WARNINGS2 = /* @__PURE__ */ new Set();
2262
3006
  function warnDeprecatedOnce2(key, message) {
2263
3007
  if (DEPRECATION_WARNINGS2.has(key)) return;
@@ -2266,7 +3010,7 @@ function warnDeprecatedOnce2(key, message) {
2266
3010
  console.warn(message);
2267
3011
  }
2268
3012
  }
2269
- function isLikelyProjectId(projectRef) {
3013
+ function isLikelyProjectId2(projectRef) {
2270
3014
  return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(projectRef);
2271
3015
  }
2272
3016
  function normalizeBaseUrl2(url) {
@@ -2388,7 +3132,7 @@ var WhisperContext = class _WhisperContext {
2388
3132
  }
2389
3133
  }
2390
3134
  }
2391
- this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
3135
+ this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS2;
2392
3136
  return this.projectCache;
2393
3137
  }
2394
3138
  async fetchResolvedProject(projectRef) {
@@ -2421,10 +3165,10 @@ var WhisperContext = class _WhisperContext {
2421
3165
  ...this.projectCache.filter((project) => project.id !== resolvedProject.id),
2422
3166
  resolvedProject
2423
3167
  ];
2424
- this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
3168
+ this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS2;
2425
3169
  return resolvedProject;
2426
3170
  }
2427
- if (isLikelyProjectId(resolvedRef)) {
3171
+ if (isLikelyProjectId2(resolvedRef)) {
2428
3172
  return {
2429
3173
  id: resolvedRef,
2430
3174
  orgId: "",
@@ -2456,7 +3200,7 @@ var WhisperContext = class _WhisperContext {
2456
3200
  message: `Project reference '${projectRef}' matched multiple projects. Use project id instead.`
2457
3201
  });
2458
3202
  }
2459
- if (isLikelyProjectId(projectRef)) {
3203
+ if (isLikelyProjectId2(projectRef)) {
2460
3204
  return projectRef;
2461
3205
  }
2462
3206
  const resolvedProject = await this.fetchResolvedProject(projectRef);
@@ -2468,7 +3212,7 @@ var WhisperContext = class _WhisperContext {
2468
3212
  ...this.projectCache.filter((project) => project.id !== resolvedProject.id),
2469
3213
  resolvedProject
2470
3214
  ];
2471
- this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
3215
+ this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS2;
2472
3216
  return resolvedProject.id;
2473
3217
  }
2474
3218
  throw new WhisperError({
@@ -2485,7 +3229,7 @@ var WhisperContext = class _WhisperContext {
2485
3229
  candidates.add(match.id);
2486
3230
  candidates.add(match.slug);
2487
3231
  candidates.add(match.name);
2488
- } else if (isLikelyProjectId(projectRef)) {
3232
+ } else if (isLikelyProjectId2(projectRef)) {
2489
3233
  const byId = projects.find((p) => p.id === projectRef);
2490
3234
  if (byId) {
2491
3235
  candidates.add(byId.slug);
@@ -2646,7 +3390,7 @@ var WhisperContext = class _WhisperContext {
2646
3390
  ...this.projectCache.filter((p) => p.id !== project.id),
2647
3391
  project
2648
3392
  ];
2649
- this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
3393
+ this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS2;
2650
3394
  return project;
2651
3395
  }
2652
3396
  async listProjects() {
@@ -2657,7 +3401,7 @@ var WhisperContext = class _WhisperContext {
2657
3401
  this.projectRefToId.set(p.slug, p.id);
2658
3402
  this.projectRefToId.set(p.name, p.id);
2659
3403
  }
2660
- this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
3404
+ this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS2;
2661
3405
  return projects;
2662
3406
  }
2663
3407
  async getProject(id) {