@usewhisper/sdk 3.3.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.js CHANGED
@@ -1599,7 +1599,582 @@ var AnalyticsModule = class {
1599
1599
  }
1600
1600
  };
1601
1601
 
1602
+ // ../src/sdk/agent-runtime.ts
1603
+ function detectBrowserStorage() {
1604
+ const maybeStorage = globalThis.localStorage;
1605
+ if (!maybeStorage || typeof maybeStorage !== "object") return null;
1606
+ const candidate = maybeStorage;
1607
+ if (typeof candidate.getItem !== "function" || typeof candidate.setItem !== "function") {
1608
+ return null;
1609
+ }
1610
+ return {
1611
+ getItem: candidate.getItem,
1612
+ setItem: candidate.setItem
1613
+ };
1614
+ }
1615
+ function createBindingStore(filePath) {
1616
+ const storage = detectBrowserStorage();
1617
+ if (storage) {
1618
+ const key = "whisper_agent_runtime_bindings";
1619
+ return {
1620
+ async load() {
1621
+ const raw = storage.getItem(key);
1622
+ if (!raw) return {};
1623
+ try {
1624
+ const parsed = JSON.parse(raw);
1625
+ return parsed && typeof parsed === "object" ? parsed : {};
1626
+ } catch {
1627
+ return {};
1628
+ }
1629
+ },
1630
+ async save(bindings) {
1631
+ storage.setItem(key, JSON.stringify(bindings));
1632
+ }
1633
+ };
1634
+ }
1635
+ return {
1636
+ async load() {
1637
+ if (typeof process === "undefined") return {};
1638
+ const fs = await import("fs/promises");
1639
+ const path = filePath || `${process.env.USERPROFILE || process.env.HOME || "."}/.whisper/sdk/agent-bindings.json`;
1640
+ try {
1641
+ const raw = await fs.readFile(path, "utf8");
1642
+ const parsed = JSON.parse(raw);
1643
+ return parsed && typeof parsed === "object" ? parsed : {};
1644
+ } catch {
1645
+ return {};
1646
+ }
1647
+ },
1648
+ async save(bindings) {
1649
+ if (typeof process === "undefined") return;
1650
+ const fs = await import("fs/promises");
1651
+ const pathMod = await import("path");
1652
+ const path = filePath || `${process.env.USERPROFILE || process.env.HOME || "."}/.whisper/sdk/agent-bindings.json`;
1653
+ await fs.mkdir(pathMod.dirname(path), { recursive: true });
1654
+ await fs.writeFile(path, JSON.stringify(bindings), "utf8");
1655
+ }
1656
+ };
1657
+ }
1658
+ function normalizeWorkspacePath(value) {
1659
+ const trimmed = value?.trim();
1660
+ if (!trimmed) return null;
1661
+ return trimmed.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
1662
+ }
1663
+ function pathBase(value) {
1664
+ const normalized = value.replace(/\\/g, "/");
1665
+ const parts = normalized.split("/").filter(Boolean);
1666
+ return parts[parts.length - 1] || normalized;
1667
+ }
1668
+ function defaultSalience(kind, success) {
1669
+ if (kind === "decision" || kind === "constraint" || kind === "failure") return "high";
1670
+ if (kind === "outcome" || kind === "task_update") return "medium";
1671
+ if (kind === "tool_result" && success === false) return "high";
1672
+ return "low";
1673
+ }
1674
+ function toMemoryType(kind) {
1675
+ if (kind === "decision" || kind === "constraint") return "instruction";
1676
+ return "event";
1677
+ }
1678
+ function summarizeLowSalience(events) {
1679
+ const lines = events.slice(-10).map((event) => {
1680
+ const fileSuffix = event.filePaths?.length ? ` [files: ${event.filePaths.join(", ")}]` : "";
1681
+ const toolSuffix = event.toolName ? ` [tool: ${event.toolName}]` : "";
1682
+ return `- ${event.kind}: ${event.summary}${fileSuffix}${toolSuffix}`;
1683
+ });
1684
+ return `Recent low-salience work:
1685
+ ${lines.join("\n")}`;
1686
+ }
1687
+ function compactWhitespace(value) {
1688
+ return value.replace(/\s+/g, " ").trim();
1689
+ }
1690
+ function withTimeout(promise, timeoutMs) {
1691
+ return new Promise((resolve, reject) => {
1692
+ const timeout = setTimeout(() => {
1693
+ reject(new Error("timeout"));
1694
+ }, timeoutMs);
1695
+ promise.then(
1696
+ (value) => {
1697
+ clearTimeout(timeout);
1698
+ resolve(value);
1699
+ },
1700
+ (error) => {
1701
+ clearTimeout(timeout);
1702
+ reject(error);
1703
+ }
1704
+ );
1705
+ });
1706
+ }
1707
+ function extractTimestamp(metadata) {
1708
+ const candidates = [
1709
+ metadata?.updatedAt,
1710
+ metadata?.createdAt,
1711
+ metadata?.timestamp,
1712
+ metadata?.event_date,
1713
+ metadata?.eventDate
1714
+ ];
1715
+ for (const value of candidates) {
1716
+ if (typeof value === "string") {
1717
+ const parsed = Date.parse(value);
1718
+ if (!Number.isNaN(parsed)) return parsed;
1719
+ }
1720
+ }
1721
+ return 0;
1722
+ }
1723
+ function salienceBoost(metadata) {
1724
+ const value = metadata?.salience;
1725
+ if (value === "high") return 0.12;
1726
+ if (value === "medium") return 0.06;
1727
+ return 0;
1728
+ }
1729
+ var WhisperAgentRuntime = class {
1730
+ constructor(args) {
1731
+ this.args = args;
1732
+ this.bindingStore = createBindingStore(args.options.bindingStorePath);
1733
+ this.topK = args.options.topK ?? 6;
1734
+ this.maxTokens = args.options.maxTokens ?? 4e3;
1735
+ this.targetRetrievalMs = args.options.targetRetrievalMs ?? 2500;
1736
+ this.hardRetrievalTimeoutMs = args.options.hardRetrievalTimeoutMs ?? 4e3;
1737
+ this.recentWorkLimit = args.options.recentWorkLimit ?? 40;
1738
+ this.baseContext = args.baseContext;
1739
+ this.clientName = args.baseContext.clientName || "whisper-agent-runtime";
1740
+ }
1741
+ bindingStore;
1742
+ topK;
1743
+ maxTokens;
1744
+ targetRetrievalMs;
1745
+ hardRetrievalTimeoutMs;
1746
+ recentWorkLimit;
1747
+ baseContext;
1748
+ clientName;
1749
+ bindings = null;
1750
+ touchedFiles = [];
1751
+ recentWork = [];
1752
+ bufferedLowSalience = [];
1753
+ lastPreparedTurn = null;
1754
+ mergedCount = 0;
1755
+ droppedCount = 0;
1756
+ lastScope = {};
1757
+ async getBindings() {
1758
+ if (!this.bindings) {
1759
+ this.bindings = await this.bindingStore.load();
1760
+ }
1761
+ return this.bindings;
1762
+ }
1763
+ pushTouchedFiles(paths) {
1764
+ if (!paths || paths.length === 0) return;
1765
+ for (const path of paths) {
1766
+ if (!path) continue;
1767
+ this.touchedFiles = [...this.touchedFiles.filter((entry) => entry !== path), path].slice(-20);
1768
+ }
1769
+ }
1770
+ pushWorkEvent(event) {
1771
+ this.recentWork = [...this.recentWork, event].slice(-this.recentWorkLimit);
1772
+ }
1773
+ makeTaskFrameQuery(input) {
1774
+ const task = compactWhitespace(input.taskSummary || "");
1775
+ const salient = this.recentWork.filter((event) => event.salience === "high").slice(-3).map((event) => `${event.kind}: ${event.summary}`);
1776
+ const files = [...input.touchedFiles || [], ...this.touchedFiles].slice(-3).map((file) => pathBase(file));
1777
+ const parts = [
1778
+ task ? `task ${task}` : "",
1779
+ salient.length > 0 ? `recent ${salient.join(" ; ")}` : "",
1780
+ files.length > 0 ? `files ${files.join(" ")}` : "",
1781
+ input.toolContext ? `tool context ${compactWhitespace(input.toolContext)}` : ""
1782
+ ].filter(Boolean);
1783
+ if (parts.length === 0) return null;
1784
+ return parts.join(" | ");
1785
+ }
1786
+ async resolveScope(overrides) {
1787
+ const merged = {
1788
+ ...this.baseContext,
1789
+ ...overrides
1790
+ };
1791
+ const normalizedWorkspace = normalizeWorkspacePath(merged.workspacePath);
1792
+ const bindings = await this.getBindings();
1793
+ const workspaceProject = normalizedWorkspace ? bindings[normalizedWorkspace] : void 0;
1794
+ const configuredProject = merged.project;
1795
+ let projectRef = configuredProject;
1796
+ let projectSource = overrides?.project ? "explicit" : "generated";
1797
+ let warning;
1798
+ if (workspaceProject) {
1799
+ projectRef = workspaceProject;
1800
+ projectSource = "workspace";
1801
+ if (configuredProject && workspaceProject !== configuredProject) {
1802
+ warning = `workspace mapping '${workspaceProject}' overrides configured project '${configuredProject}'`;
1803
+ }
1804
+ } else if (configuredProject) {
1805
+ projectRef = configuredProject;
1806
+ projectSource = overrides?.project ? "explicit" : "config";
1807
+ }
1808
+ const project = (await this.args.adapter.resolveProject(projectRef)).id;
1809
+ if (normalizedWorkspace) {
1810
+ bindings[normalizedWorkspace] = project;
1811
+ await this.bindingStore.save(bindings);
1812
+ }
1813
+ const scope = {
1814
+ ...merged,
1815
+ project,
1816
+ userId: merged.userId || `${this.clientName}-user`,
1817
+ sessionId: merged.sessionId || `sess_${stableHash(`${this.clientName}_${normalizedWorkspace || "default"}`)}`
1818
+ };
1819
+ this.lastScope = {
1820
+ project: scope.project,
1821
+ userId: scope.userId,
1822
+ sessionId: scope.sessionId,
1823
+ source: projectSource,
1824
+ warning
1825
+ };
1826
+ return { scope, projectSource, warning };
1827
+ }
1828
+ async runBranch(name, task) {
1829
+ const startedAt = Date.now();
1830
+ try {
1831
+ const value = await withTimeout(task(), this.hardRetrievalTimeoutMs);
1832
+ return {
1833
+ name,
1834
+ status: "ok",
1835
+ durationMs: Date.now() - startedAt,
1836
+ value
1837
+ };
1838
+ } catch (error) {
1839
+ const durationMs = Date.now() - startedAt;
1840
+ const reason = error instanceof Error ? error.message : String(error);
1841
+ return {
1842
+ name,
1843
+ status: reason === "timeout" ? "timeout" : "error",
1844
+ durationMs,
1845
+ reason
1846
+ };
1847
+ }
1848
+ }
1849
+ contextItems(result, sourceQuery) {
1850
+ return (result.results || []).map((item) => ({
1851
+ id: item.id,
1852
+ content: item.content,
1853
+ type: "project",
1854
+ score: item.score ?? 0,
1855
+ sourceQuery,
1856
+ metadata: item.metadata || {}
1857
+ }));
1858
+ }
1859
+ memoryItems(result, sourceQuery) {
1860
+ return (result.results || []).map((item, index) => ({
1861
+ id: item.memory?.id || item.chunk?.id || `${sourceQuery}_memory_${index}`,
1862
+ content: item.chunk?.content || item.memory?.content || "",
1863
+ type: "memory",
1864
+ score: item.similarity ?? 0,
1865
+ sourceQuery,
1866
+ metadata: {
1867
+ ...item.chunk?.metadata || {},
1868
+ ...item.memory?.temporal || {},
1869
+ confidence: item.memory?.confidence
1870
+ }
1871
+ })).filter((item) => item.content);
1872
+ }
1873
+ rerank(items) {
1874
+ const deduped = /* @__PURE__ */ new Map();
1875
+ for (const item of items) {
1876
+ const key = `${item.id}:${item.content.toLowerCase()}`;
1877
+ const recency = extractTimestamp(item.metadata) > 0 ? 0.04 : 0;
1878
+ const queryBonus = item.sourceQuery === "primary" ? 0.08 : item.sourceQuery === "task_frame" ? 0.04 : 0.03;
1879
+ const next = {
1880
+ ...item,
1881
+ score: item.score + queryBonus + salienceBoost(item.metadata) + recency
1882
+ };
1883
+ const existing = deduped.get(key);
1884
+ if (!existing || next.score > existing.score) {
1885
+ deduped.set(key, next);
1886
+ }
1887
+ }
1888
+ return [...deduped.values()].sort((left, right) => right.score - left.score);
1889
+ }
1890
+ buildContext(items) {
1891
+ const maxChars = this.maxTokens * 4;
1892
+ const lines = [];
1893
+ let used = 0;
1894
+ for (const item of items) {
1895
+ const label = item.type === "memory" ? "memory" : "context";
1896
+ const content = compactWhitespace(item.content);
1897
+ if (!content) continue;
1898
+ const line = `[${label}] ${content}`;
1899
+ if (used + line.length > maxChars) break;
1900
+ lines.push(line);
1901
+ used += line.length;
1902
+ }
1903
+ if (lines.length === 0) return "";
1904
+ return `Relevant context:
1905
+ ${lines.join("\n")}`;
1906
+ }
1907
+ async bootstrap(context = {}) {
1908
+ const { scope, warning } = await this.resolveScope(context);
1909
+ const warnings = warning ? [warning] : [];
1910
+ const startedAt = Date.now();
1911
+ const branches = await Promise.all([
1912
+ this.runBranch("session_recent", () => this.args.adapter.getSessionMemories({
1913
+ project: scope.project,
1914
+ session_id: scope.sessionId,
1915
+ include_pending: true,
1916
+ limit: 12
1917
+ })),
1918
+ this.runBranch("user_profile", () => scope.userId ? this.args.adapter.getUserProfile({
1919
+ project: scope.project,
1920
+ user_id: scope.userId,
1921
+ include_pending: true,
1922
+ memory_types: "preference,instruction,goal"
1923
+ }) : Promise.resolve({ user_id: scope.userId, memories: [], count: 0 })),
1924
+ this.runBranch("project_rules", () => this.args.adapter.query({
1925
+ project: scope.project,
1926
+ query: "project rules instructions constraints conventions open threads",
1927
+ top_k: this.topK,
1928
+ include_memories: false,
1929
+ user_id: scope.userId,
1930
+ session_id: scope.sessionId,
1931
+ max_tokens: this.maxTokens,
1932
+ compress: true,
1933
+ compression_strategy: "adaptive"
1934
+ }))
1935
+ ]);
1936
+ const items = [];
1937
+ const branchStatus = {};
1938
+ for (const branch of branches) {
1939
+ branchStatus[branch.name] = branch.status;
1940
+ if (branch.status !== "ok") {
1941
+ if (branch.reason) warnings.push(`${branch.name}:${branch.reason}`);
1942
+ continue;
1943
+ }
1944
+ if (branch.name === "project_rules") {
1945
+ items.push(...this.contextItems(branch.value, "bootstrap"));
1946
+ continue;
1947
+ }
1948
+ const records = branch.value.memories || [];
1949
+ items.push(...records.map((memory, index) => ({
1950
+ id: String(memory.id || `${branch.name}_${index}`),
1951
+ content: String(memory.content || ""),
1952
+ type: "memory",
1953
+ score: 0.4,
1954
+ sourceQuery: "bootstrap",
1955
+ metadata: memory
1956
+ })).filter((item) => item.content));
1957
+ }
1958
+ const ranked = this.rerank(items).slice(0, this.topK * 2);
1959
+ const prepared = {
1960
+ scope,
1961
+ retrieval: {
1962
+ primaryQuery: "bootstrap",
1963
+ taskFrameQuery: null,
1964
+ warnings,
1965
+ degraded: warnings.length > 0,
1966
+ degradedReason: warnings.length > 0 ? "partial_bootstrap" : void 0,
1967
+ durationMs: Date.now() - startedAt,
1968
+ targetBudgetMs: this.targetRetrievalMs,
1969
+ hardTimeoutMs: this.hardRetrievalTimeoutMs,
1970
+ branchStatus
1971
+ },
1972
+ context: this.buildContext(ranked),
1973
+ items: ranked
1974
+ };
1975
+ this.lastPreparedTurn = prepared.retrieval;
1976
+ return prepared;
1977
+ }
1978
+ async beforeTurn(input, context = {}) {
1979
+ this.pushTouchedFiles(input.touchedFiles);
1980
+ const { scope, warning } = await this.resolveScope(context);
1981
+ const primaryQuery = compactWhitespace(input.userMessage);
1982
+ const taskFrameQuery = this.makeTaskFrameQuery(input);
1983
+ const warnings = warning ? [warning] : [];
1984
+ const startedAt = Date.now();
1985
+ const branches = await Promise.all([
1986
+ this.runBranch("context_primary", () => this.args.adapter.query({
1987
+ project: scope.project,
1988
+ query: primaryQuery,
1989
+ top_k: this.topK,
1990
+ include_memories: false,
1991
+ user_id: scope.userId,
1992
+ session_id: scope.sessionId,
1993
+ max_tokens: this.maxTokens,
1994
+ compress: true,
1995
+ compression_strategy: "adaptive"
1996
+ })),
1997
+ this.runBranch("memory_primary", () => this.args.adapter.searchMemories({
1998
+ project: scope.project,
1999
+ query: primaryQuery,
2000
+ user_id: scope.userId,
2001
+ session_id: scope.sessionId,
2002
+ top_k: this.topK,
2003
+ include_pending: true,
2004
+ profile: "balanced"
2005
+ })),
2006
+ taskFrameQuery ? this.runBranch("context_task_frame", () => this.args.adapter.query({
2007
+ project: scope.project,
2008
+ query: taskFrameQuery,
2009
+ top_k: this.topK,
2010
+ include_memories: false,
2011
+ user_id: scope.userId,
2012
+ session_id: scope.sessionId,
2013
+ max_tokens: this.maxTokens,
2014
+ compress: true,
2015
+ compression_strategy: "adaptive"
2016
+ })) : Promise.resolve({
2017
+ name: "context_task_frame",
2018
+ status: "skipped",
2019
+ durationMs: 0
2020
+ }),
2021
+ taskFrameQuery ? this.runBranch("memory_task_frame", () => this.args.adapter.searchMemories({
2022
+ project: scope.project,
2023
+ query: taskFrameQuery,
2024
+ user_id: scope.userId,
2025
+ session_id: scope.sessionId,
2026
+ top_k: this.topK,
2027
+ include_pending: true,
2028
+ profile: "balanced"
2029
+ })) : Promise.resolve({
2030
+ name: "memory_task_frame",
2031
+ status: "skipped",
2032
+ durationMs: 0
2033
+ })
2034
+ ]);
2035
+ const branchStatus = {};
2036
+ const collected = [];
2037
+ let okCount = 0;
2038
+ for (const branch of branches) {
2039
+ branchStatus[branch.name] = branch.status;
2040
+ if (branch.status !== "ok") {
2041
+ if (branch.status !== "skipped" && branch.reason) warnings.push(`${branch.name}:${branch.reason}`);
2042
+ continue;
2043
+ }
2044
+ okCount += 1;
2045
+ if (branch.name.startsWith("context")) {
2046
+ collected.push(...this.contextItems(
2047
+ branch.value,
2048
+ branch.name.includes("task_frame") ? "task_frame" : "primary"
2049
+ ));
2050
+ } else {
2051
+ collected.push(...this.memoryItems(
2052
+ branch.value,
2053
+ branch.name.includes("task_frame") ? "task_frame" : "primary"
2054
+ ));
2055
+ }
2056
+ }
2057
+ const ranked = this.rerank(collected).slice(0, this.topK * 2);
2058
+ const prepared = {
2059
+ scope,
2060
+ retrieval: {
2061
+ primaryQuery,
2062
+ taskFrameQuery,
2063
+ warnings,
2064
+ degraded: okCount < branches.filter((branch) => branch.status !== "skipped").length,
2065
+ degradedReason: okCount === 0 ? "all_retrieval_failed" : warnings.length > 0 ? "partial_retrieval_failed" : void 0,
2066
+ durationMs: Date.now() - startedAt,
2067
+ targetBudgetMs: this.targetRetrievalMs,
2068
+ hardTimeoutMs: this.hardRetrievalTimeoutMs,
2069
+ branchStatus
2070
+ },
2071
+ context: this.buildContext(ranked),
2072
+ items: ranked
2073
+ };
2074
+ this.lastPreparedTurn = prepared.retrieval;
2075
+ return prepared;
2076
+ }
2077
+ async recordWork(event, context = {}) {
2078
+ const normalized = {
2079
+ ...event,
2080
+ salience: event.salience || defaultSalience(event.kind, event.success),
2081
+ timestamp: event.timestamp || nowIso()
2082
+ };
2083
+ this.pushTouchedFiles(normalized.filePaths);
2084
+ this.pushWorkEvent(normalized);
2085
+ if (normalized.salience === "low") {
2086
+ this.bufferedLowSalience = [...this.bufferedLowSalience, normalized].slice(-20);
2087
+ return { success: true, buffered: true };
2088
+ }
2089
+ const { scope } = await this.resolveScope(context);
2090
+ return this.args.adapter.addMemory({
2091
+ project: scope.project,
2092
+ user_id: scope.userId,
2093
+ session_id: scope.sessionId,
2094
+ content: `${normalized.kind}: ${normalized.summary}${normalized.details ? ` (${normalized.details})` : ""}`,
2095
+ memory_type: toMemoryType(normalized.kind),
2096
+ event_date: normalized.timestamp,
2097
+ write_mode: "async",
2098
+ metadata: {
2099
+ runtime_auto: true,
2100
+ client_name: this.clientName,
2101
+ work_event_kind: normalized.kind,
2102
+ salience: normalized.salience,
2103
+ file_paths: normalized.filePaths || [],
2104
+ tool_name: normalized.toolName,
2105
+ success: normalized.success
2106
+ }
2107
+ });
2108
+ }
2109
+ async afterTurn(input, context = {}) {
2110
+ this.pushTouchedFiles(input.touchedFiles);
2111
+ const { scope } = await this.resolveScope(context);
2112
+ const result = await this.args.adapter.ingestSession({
2113
+ project: scope.project,
2114
+ session_id: scope.sessionId,
2115
+ user_id: scope.userId,
2116
+ messages: [
2117
+ { role: "user", content: input.userMessage, timestamp: nowIso() },
2118
+ { role: "assistant", content: input.assistantMessage, timestamp: nowIso() }
2119
+ ],
2120
+ write_mode: "async"
2121
+ });
2122
+ this.mergedCount += result.memories_invalidated || 0;
2123
+ return {
2124
+ success: Boolean(result.success),
2125
+ sessionIngested: true,
2126
+ memoriesCreated: result.memories_created || 0,
2127
+ relationsCreated: result.relations_created || 0,
2128
+ invalidatedCount: result.memories_invalidated || 0,
2129
+ mergedCount: result.memories_invalidated || 0,
2130
+ droppedCount: 0,
2131
+ warnings: result.errors || []
2132
+ };
2133
+ }
2134
+ async flush(reason = "manual", context = {}) {
2135
+ if (this.bufferedLowSalience.length > 0) {
2136
+ const { scope } = await this.resolveScope(context);
2137
+ const summary = summarizeLowSalience(this.bufferedLowSalience);
2138
+ await this.args.adapter.addMemory({
2139
+ project: scope.project,
2140
+ user_id: scope.userId,
2141
+ session_id: scope.sessionId,
2142
+ content: summary,
2143
+ memory_type: "event",
2144
+ write_mode: "async",
2145
+ metadata: {
2146
+ runtime_auto: true,
2147
+ client_name: this.clientName,
2148
+ flush_reason: reason,
2149
+ salience: "low",
2150
+ summarized_count: this.bufferedLowSalience.length
2151
+ }
2152
+ });
2153
+ this.bufferedLowSalience = [];
2154
+ }
2155
+ await this.args.adapter.flushQueue();
2156
+ return this.status();
2157
+ }
2158
+ status() {
2159
+ return {
2160
+ clientName: this.clientName,
2161
+ scope: this.lastScope,
2162
+ queue: this.args.adapter.queueStatus(),
2163
+ retrieval: this.lastPreparedTurn,
2164
+ counters: {
2165
+ mergedCount: this.mergedCount,
2166
+ droppedCount: this.droppedCount,
2167
+ bufferedLowSalience: this.bufferedLowSalience.length
2168
+ }
2169
+ };
2170
+ }
2171
+ };
2172
+
1602
2173
  // ../src/sdk/whisper.ts
2174
+ var PROJECT_CACHE_TTL_MS = 3e4;
2175
+ function isLikelyProjectId(projectRef) {
2176
+ 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);
2177
+ }
1603
2178
  var WhisperClient = class _WhisperClient {
1604
2179
  constructor(config) {
1605
2180
  this.config = config;
@@ -1619,7 +2194,7 @@ var WhisperClient = class _WhisperClient {
1619
2194
  config.cache?.ttlMs ?? 7e3,
1620
2195
  config.cache?.capacity ?? 500
1621
2196
  );
1622
- const queueStore = config.queue?.persistence === "storage" ? createStorageQueueStore() : config.queue?.persistence === "file" && config.queue.filePath ? createFileQueueStore(config.queue.filePath) : new InMemoryQueueStore();
2197
+ const queueStore = this.createQueueStore(config);
1623
2198
  this.writeQueue = new WriteQueue({
1624
2199
  store: queueStore,
1625
2200
  maxBatchSize: config.queue?.maxBatchSize ?? 50,
@@ -1752,6 +2327,9 @@ var WhisperClient = class _WhisperClient {
1752
2327
  sessionModule;
1753
2328
  profileModule;
1754
2329
  analyticsModule;
2330
+ projectRefToId = /* @__PURE__ */ new Map();
2331
+ projectCache = [];
2332
+ projectCacheExpiresAt = 0;
1755
2333
  static fromEnv(overrides = {}) {
1756
2334
  const env = typeof process !== "undefined" ? process.env : {};
1757
2335
  const apiKey = overrides.apiKey || env.WHISPER_API_KEY || env.USEWHISPER_API_KEY || env.API_KEY;
@@ -1765,6 +2343,172 @@ var WhisperClient = class _WhisperClient {
1765
2343
  ...overrides
1766
2344
  });
1767
2345
  }
2346
+ createQueueStore(config) {
2347
+ const persistence = config.queue?.persistence || this.defaultQueuePersistence();
2348
+ if (persistence === "storage") {
2349
+ return createStorageQueueStore();
2350
+ }
2351
+ if (persistence === "file") {
2352
+ const filePath = config.queue?.filePath || this.defaultQueueFilePath();
2353
+ if (filePath) {
2354
+ return createFileQueueStore(filePath);
2355
+ }
2356
+ }
2357
+ return new InMemoryQueueStore();
2358
+ }
2359
+ defaultQueuePersistence() {
2360
+ const maybeWindow = globalThis.window;
2361
+ if (maybeWindow && typeof maybeWindow === "object") {
2362
+ const maybeStorage = globalThis.localStorage;
2363
+ return maybeStorage && typeof maybeStorage === "object" ? "storage" : "memory";
2364
+ }
2365
+ return "file";
2366
+ }
2367
+ defaultQueueFilePath() {
2368
+ if (typeof process === "undefined") return void 0;
2369
+ const path = process.env.WHISPER_QUEUE_FILE_PATH;
2370
+ if (path) return path;
2371
+ const home = process.env.USERPROFILE || process.env.HOME;
2372
+ if (!home) return void 0;
2373
+ const normalizedHome = home.replace(/[\\\/]+$/, "");
2374
+ return `${normalizedHome}/.whisper/sdk/queue.json`;
2375
+ }
2376
+ getRequiredProject(project) {
2377
+ const resolved = project || this.config.project;
2378
+ if (!resolved) {
2379
+ throw new RuntimeClientError({
2380
+ code: "MISSING_PROJECT",
2381
+ message: "Project is required",
2382
+ retryable: false
2383
+ });
2384
+ }
2385
+ return resolved;
2386
+ }
2387
+ async refreshProjectCache(force = false) {
2388
+ if (!force && Date.now() < this.projectCacheExpiresAt && this.projectCache.length > 0) {
2389
+ return this.projectCache;
2390
+ }
2391
+ const response = await this.runtimeClient.request({
2392
+ endpoint: "/v1/projects",
2393
+ method: "GET",
2394
+ operation: "get",
2395
+ idempotent: true
2396
+ });
2397
+ this.projectRefToId.clear();
2398
+ this.projectCache = response.data?.projects || [];
2399
+ for (const project of this.projectCache) {
2400
+ this.projectRefToId.set(project.id, project.id);
2401
+ this.projectRefToId.set(project.slug, project.id);
2402
+ this.projectRefToId.set(project.name, project.id);
2403
+ }
2404
+ this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
2405
+ return this.projectCache;
2406
+ }
2407
+ async fetchResolvedProject(projectRef) {
2408
+ try {
2409
+ const response = await this.runtimeClient.request({
2410
+ endpoint: `/v1/projects/resolve?project=${encodeURIComponent(projectRef)}`,
2411
+ method: "GET",
2412
+ operation: "get",
2413
+ idempotent: true
2414
+ });
2415
+ return response.data?.resolved || null;
2416
+ } catch (error) {
2417
+ if (error instanceof RuntimeClientError && error.status === 404) {
2418
+ return null;
2419
+ }
2420
+ throw error;
2421
+ }
2422
+ }
2423
+ async resolveProject(projectRef) {
2424
+ const resolvedRef = this.getRequiredProject(projectRef);
2425
+ const cachedProjects = await this.refreshProjectCache(false);
2426
+ const cachedProject = cachedProjects.find(
2427
+ (project) => project.id === resolvedRef || project.slug === resolvedRef || project.name === resolvedRef
2428
+ );
2429
+ if (cachedProject) {
2430
+ return cachedProject;
2431
+ }
2432
+ const resolvedProject = await this.fetchResolvedProject(resolvedRef);
2433
+ if (resolvedProject) {
2434
+ this.projectRefToId.set(resolvedProject.id, resolvedProject.id);
2435
+ this.projectRefToId.set(resolvedProject.slug, resolvedProject.id);
2436
+ this.projectRefToId.set(resolvedProject.name, resolvedProject.id);
2437
+ this.projectCache = [
2438
+ ...this.projectCache.filter((project) => project.id !== resolvedProject.id),
2439
+ resolvedProject
2440
+ ];
2441
+ this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
2442
+ return resolvedProject;
2443
+ }
2444
+ if (isLikelyProjectId(resolvedRef)) {
2445
+ return {
2446
+ id: resolvedRef,
2447
+ orgId: "",
2448
+ name: resolvedRef,
2449
+ slug: resolvedRef,
2450
+ createdAt: (/* @__PURE__ */ new Date(0)).toISOString(),
2451
+ updatedAt: (/* @__PURE__ */ new Date(0)).toISOString()
2452
+ };
2453
+ }
2454
+ throw new RuntimeClientError({
2455
+ code: "PROJECT_NOT_FOUND",
2456
+ message: `Project '${resolvedRef}' not found`,
2457
+ retryable: false
2458
+ });
2459
+ }
2460
+ async query(params) {
2461
+ const project = (await this.resolveProject(params.project)).id;
2462
+ const response = await this.runtimeClient.request({
2463
+ endpoint: "/v1/context/query",
2464
+ method: "POST",
2465
+ operation: "search",
2466
+ body: {
2467
+ ...params,
2468
+ project
2469
+ },
2470
+ idempotent: true
2471
+ });
2472
+ return response.data;
2473
+ }
2474
+ async ingestSession(params) {
2475
+ const project = (await this.resolveProject(params.project)).id;
2476
+ const response = await this.runtimeClient.request({
2477
+ endpoint: "/v1/memory/ingest/session",
2478
+ method: "POST",
2479
+ operation: "session",
2480
+ body: {
2481
+ ...params,
2482
+ project
2483
+ }
2484
+ });
2485
+ return response.data;
2486
+ }
2487
+ createAgentRuntime(options = {}) {
2488
+ const baseContext = {
2489
+ workspacePath: options.workspacePath,
2490
+ project: options.project || this.config.project,
2491
+ userId: options.userId,
2492
+ sessionId: options.sessionId,
2493
+ traceId: options.traceId,
2494
+ clientName: options.clientName
2495
+ };
2496
+ return new WhisperAgentRuntime({
2497
+ baseContext,
2498
+ options,
2499
+ adapter: {
2500
+ resolveProject: (project) => this.resolveProject(project),
2501
+ query: (params) => this.query(params),
2502
+ ingestSession: (params) => this.ingestSession(params),
2503
+ getSessionMemories: (params) => this.memory.getSessionMemories(params),
2504
+ getUserProfile: (params) => this.memory.getUserProfile(params),
2505
+ searchMemories: (params) => this.memory.search(params),
2506
+ addMemory: (params) => this.memory.add(params),
2507
+ queueStatus: () => this.queue.status(),
2508
+ flushQueue: () => this.queue.flush()
2509
+ }
2510
+ });
2511
+ }
1768
2512
  withRunContext(context) {
1769
2513
  const base = this;
1770
2514
  return {
@@ -2306,7 +3050,7 @@ var DEFAULT_MAX_ATTEMPTS = 3;
2306
3050
  var DEFAULT_BASE_DELAY_MS = 250;
2307
3051
  var DEFAULT_MAX_DELAY_MS = 2e3;
2308
3052
  var DEFAULT_TIMEOUT_MS = 15e3;
2309
- var PROJECT_CACHE_TTL_MS = 3e4;
3053
+ var PROJECT_CACHE_TTL_MS2 = 3e4;
2310
3054
  var DEPRECATION_WARNINGS2 = /* @__PURE__ */ new Set();
2311
3055
  function warnDeprecatedOnce2(key, message) {
2312
3056
  if (DEPRECATION_WARNINGS2.has(key)) return;
@@ -2315,7 +3059,7 @@ function warnDeprecatedOnce2(key, message) {
2315
3059
  console.warn(message);
2316
3060
  }
2317
3061
  }
2318
- function isLikelyProjectId(projectRef) {
3062
+ function isLikelyProjectId2(projectRef) {
2319
3063
  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);
2320
3064
  }
2321
3065
  function normalizeBaseUrl2(url) {
@@ -2437,7 +3181,7 @@ var WhisperContext = class _WhisperContext {
2437
3181
  }
2438
3182
  }
2439
3183
  }
2440
- this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
3184
+ this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS2;
2441
3185
  return this.projectCache;
2442
3186
  }
2443
3187
  async fetchResolvedProject(projectRef) {
@@ -2470,10 +3214,10 @@ var WhisperContext = class _WhisperContext {
2470
3214
  ...this.projectCache.filter((project) => project.id !== resolvedProject.id),
2471
3215
  resolvedProject
2472
3216
  ];
2473
- this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
3217
+ this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS2;
2474
3218
  return resolvedProject;
2475
3219
  }
2476
- if (isLikelyProjectId(resolvedRef)) {
3220
+ if (isLikelyProjectId2(resolvedRef)) {
2477
3221
  return {
2478
3222
  id: resolvedRef,
2479
3223
  orgId: "",
@@ -2505,7 +3249,7 @@ var WhisperContext = class _WhisperContext {
2505
3249
  message: `Project reference '${projectRef}' matched multiple projects. Use project id instead.`
2506
3250
  });
2507
3251
  }
2508
- if (isLikelyProjectId(projectRef)) {
3252
+ if (isLikelyProjectId2(projectRef)) {
2509
3253
  return projectRef;
2510
3254
  }
2511
3255
  const resolvedProject = await this.fetchResolvedProject(projectRef);
@@ -2517,7 +3261,7 @@ var WhisperContext = class _WhisperContext {
2517
3261
  ...this.projectCache.filter((project) => project.id !== resolvedProject.id),
2518
3262
  resolvedProject
2519
3263
  ];
2520
- this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
3264
+ this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS2;
2521
3265
  return resolvedProject.id;
2522
3266
  }
2523
3267
  throw new WhisperError({
@@ -2534,7 +3278,7 @@ var WhisperContext = class _WhisperContext {
2534
3278
  candidates.add(match.id);
2535
3279
  candidates.add(match.slug);
2536
3280
  candidates.add(match.name);
2537
- } else if (isLikelyProjectId(projectRef)) {
3281
+ } else if (isLikelyProjectId2(projectRef)) {
2538
3282
  const byId = projects.find((p) => p.id === projectRef);
2539
3283
  if (byId) {
2540
3284
  candidates.add(byId.slug);
@@ -2695,7 +3439,7 @@ var WhisperContext = class _WhisperContext {
2695
3439
  ...this.projectCache.filter((p) => p.id !== project.id),
2696
3440
  project
2697
3441
  ];
2698
- this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
3442
+ this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS2;
2699
3443
  return project;
2700
3444
  }
2701
3445
  async listProjects() {
@@ -2706,7 +3450,7 @@ var WhisperContext = class _WhisperContext {
2706
3450
  this.projectRefToId.set(p.slug, p.id);
2707
3451
  this.projectRefToId.set(p.name, p.id);
2708
3452
  }
2709
- this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
3453
+ this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS2;
2710
3454
  return projects;
2711
3455
  }
2712
3456
  async getProject(id) {