stashes 0.1.28 → 0.1.29

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/dist/cli.js CHANGED
@@ -1537,7 +1537,10 @@ class StashService {
1537
1537
  selectedComponent = null;
1538
1538
  messageQueue = [];
1539
1539
  isProcessingMessage = false;
1540
+ activeChatId = null;
1540
1541
  chatSessions = new Map;
1542
+ stashPollTimer = null;
1543
+ knownStashIds = new Set;
1541
1544
  constructor(projectPath, worktreeManager, persistence, broadcast) {
1542
1545
  this.projectPath = projectPath;
1543
1546
  this.worktreeManager = worktreeManager;
@@ -1545,6 +1548,9 @@ class StashService {
1545
1548
  this.broadcast = broadcast;
1546
1549
  this.previewPool = new PreviewPool(worktreeManager, broadcast);
1547
1550
  }
1551
+ getActiveChatId() {
1552
+ return this.activeChatId;
1553
+ }
1548
1554
  setSelectedComponent(component) {
1549
1555
  this.selectedComponent = component;
1550
1556
  if (component.filePath === "auto-detect") {
@@ -1599,8 +1605,10 @@ class StashService {
1599
1605
  this.isProcessingMessage = true;
1600
1606
  while (this.messageQueue.length > 0) {
1601
1607
  const msg = this.messageQueue.shift();
1608
+ this.activeChatId = msg.chatId;
1602
1609
  await this.processMessage(msg.projectId, msg.chatId, msg.message, msg.referenceStashIds, msg.componentContext);
1603
1610
  }
1611
+ this.activeChatId = null;
1604
1612
  this.isProcessingMessage = false;
1605
1613
  }
1606
1614
  async processMessage(projectId, chatId, message, referenceStashIds, componentContext) {
@@ -1660,20 +1668,22 @@ ${sourceCode.substring(0, 3000)}
1660
1668
  const aiProcess = startAiProcess("chat", chatPrompt, this.projectPath, existingSessionId);
1661
1669
  let thinkingBuf = "";
1662
1670
  let textBuf = "";
1663
- const pendingMessages = [];
1664
1671
  const now = new Date().toISOString();
1665
- function flushThinking() {
1672
+ const save = (msg) => {
1673
+ this.persistence.saveChatMessage(projectId, chatId, msg);
1674
+ };
1675
+ const flushThinking = () => {
1666
1676
  if (!thinkingBuf)
1667
1677
  return;
1668
- pendingMessages.push({ id: crypto.randomUUID(), role: "assistant", content: thinkingBuf, type: "thinking", createdAt: now });
1678
+ save({ id: crypto.randomUUID(), role: "assistant", content: thinkingBuf, type: "thinking", createdAt: now });
1669
1679
  thinkingBuf = "";
1670
- }
1671
- function flushText() {
1680
+ };
1681
+ const flushText = () => {
1672
1682
  if (!textBuf)
1673
1683
  return;
1674
- pendingMessages.push({ id: crypto.randomUUID(), role: "assistant", content: textBuf, type: "text", createdAt: now });
1684
+ save({ id: crypto.randomUUID(), role: "assistant", content: textBuf, type: "text", createdAt: now });
1675
1685
  textBuf = "";
1676
- }
1686
+ };
1677
1687
  try {
1678
1688
  for await (const chunk of parseClaudeStream(aiProcess.process)) {
1679
1689
  if (chunk.type === "session_id" && chunk.sessionId) {
@@ -1707,7 +1717,7 @@ ${sourceCode.substring(0, 3000)}
1707
1717
  toolName = parsed.tool ?? "unknown";
1708
1718
  toolParams = parsed.input ?? {};
1709
1719
  } catch {}
1710
- pendingMessages.push({
1720
+ save({
1711
1721
  id: crypto.randomUUID(),
1712
1722
  role: "assistant",
1713
1723
  content: chunk.content,
@@ -1726,7 +1736,11 @@ ${sourceCode.substring(0, 3000)}
1726
1736
  toolParams,
1727
1737
  toolStatus: "running"
1728
1738
  });
1739
+ if (toolName.includes("stashes_generate") || toolName.includes("stashes_vary")) {
1740
+ this.startStashPoll(projectId, chatId);
1741
+ }
1729
1742
  } else if (chunk.type === "tool_result") {
1743
+ this.stopStashPoll();
1730
1744
  let toolResult = chunk.content;
1731
1745
  let isError = false;
1732
1746
  try {
@@ -1734,7 +1748,7 @@ ${sourceCode.substring(0, 3000)}
1734
1748
  toolResult = parsed.result ?? chunk.content;
1735
1749
  isError = !!parsed.is_error;
1736
1750
  } catch {}
1737
- pendingMessages.push({
1751
+ save({
1738
1752
  id: crypto.randomUUID(),
1739
1753
  role: "assistant",
1740
1754
  content: chunk.content,
@@ -1756,10 +1770,7 @@ ${sourceCode.substring(0, 3000)}
1756
1770
  await aiProcess.process.exited;
1757
1771
  flushThinking();
1758
1772
  flushText();
1759
- for (const msg of pendingMessages) {
1760
- this.persistence.saveChatMessage(projectId, chatId, msg);
1761
- }
1762
- this.syncStashesFromDisk(projectId);
1773
+ this.syncStashesFromDisk(projectId, chatId);
1763
1774
  } catch (err) {
1764
1775
  this.broadcast({
1765
1776
  type: "ai_stream",
@@ -1768,12 +1779,17 @@ ${sourceCode.substring(0, 3000)}
1768
1779
  source: "chat"
1769
1780
  });
1770
1781
  } finally {
1782
+ this.stopStashPoll();
1771
1783
  killAiProcess("chat");
1772
1784
  }
1773
1785
  }
1774
- syncStashesFromDisk(projectId) {
1786
+ syncStashesFromDisk(projectId, chatId) {
1775
1787
  const diskStashes = this.persistence.listStashes(projectId);
1776
1788
  for (const stash of diskStashes) {
1789
+ if (chatId && !stash.originChatId && !this.knownStashIds.has(stash.id)) {
1790
+ const updated = { ...stash, originChatId: chatId };
1791
+ this.persistence.saveStash(updated);
1792
+ }
1777
1793
  this.broadcast({
1778
1794
  type: "stash:status",
1779
1795
  stashId: stash.id,
@@ -1788,6 +1804,48 @@ ${sourceCode.substring(0, 3000)}
1788
1804
  screenshots: stash.screenshots?.length ? [...stash.screenshots] : undefined
1789
1805
  });
1790
1806
  }
1807
+ this.knownStashIds.add(stash.id);
1808
+ }
1809
+ }
1810
+ startStashPoll(projectId, chatId) {
1811
+ this.stopStashPoll();
1812
+ const lastStatus = new Map;
1813
+ for (const s of this.persistence.listStashes(projectId)) {
1814
+ this.knownStashIds.add(s.id);
1815
+ lastStatus.set(s.id, s.status);
1816
+ }
1817
+ this.stashPollTimer = setInterval(() => {
1818
+ const stashes = this.persistence.listStashes(projectId);
1819
+ for (const stash of stashes) {
1820
+ const prev = lastStatus.get(stash.id);
1821
+ if (!prev || prev !== stash.status) {
1822
+ if (chatId && !stash.originChatId && !prev) {
1823
+ this.persistence.saveStash({ ...stash, originChatId: chatId });
1824
+ }
1825
+ this.broadcast({
1826
+ type: "stash:status",
1827
+ stashId: stash.id,
1828
+ status: stash.status,
1829
+ number: stash.number
1830
+ });
1831
+ if (stash.screenshotUrl && prev !== stash.status) {
1832
+ this.broadcast({
1833
+ type: "stash:screenshot",
1834
+ stashId: stash.id,
1835
+ url: stash.screenshotUrl,
1836
+ screenshots: stash.screenshots?.length ? [...stash.screenshots] : undefined
1837
+ });
1838
+ }
1839
+ lastStatus.set(stash.id, stash.status);
1840
+ this.knownStashIds.add(stash.id);
1841
+ }
1842
+ }
1843
+ }, 2000);
1844
+ }
1845
+ stopStashPoll() {
1846
+ if (this.stashPollTimer) {
1847
+ clearInterval(this.stashPollTimer);
1848
+ this.stashPollTimer = null;
1791
1849
  }
1792
1850
  }
1793
1851
  progressToBroadcast(event) {
@@ -1914,6 +1972,10 @@ function createWebSocketHandler(projectPath, userDevPort, appProxyPort) {
1914
1972
  projectId: project.id,
1915
1973
  projectName: project.name
1916
1974
  }));
1975
+ const activeChatId = stashService.getActiveChatId();
1976
+ if (activeChatId) {
1977
+ ws.send(JSON.stringify({ type: "processing", chatId: activeChatId }));
1978
+ }
1917
1979
  },
1918
1980
  async message(ws, message) {
1919
1981
  const raw = typeof message === "string" ? message : new TextDecoder().decode(message);
package/dist/mcp.js CHANGED
@@ -1733,7 +1733,10 @@ class StashService {
1733
1733
  selectedComponent = null;
1734
1734
  messageQueue = [];
1735
1735
  isProcessingMessage = false;
1736
+ activeChatId = null;
1736
1737
  chatSessions = new Map;
1738
+ stashPollTimer = null;
1739
+ knownStashIds = new Set;
1737
1740
  constructor(projectPath, worktreeManager, persistence, broadcast) {
1738
1741
  this.projectPath = projectPath;
1739
1742
  this.worktreeManager = worktreeManager;
@@ -1741,6 +1744,9 @@ class StashService {
1741
1744
  this.broadcast = broadcast;
1742
1745
  this.previewPool = new PreviewPool(worktreeManager, broadcast);
1743
1746
  }
1747
+ getActiveChatId() {
1748
+ return this.activeChatId;
1749
+ }
1744
1750
  setSelectedComponent(component) {
1745
1751
  this.selectedComponent = component;
1746
1752
  if (component.filePath === "auto-detect") {
@@ -1795,8 +1801,10 @@ class StashService {
1795
1801
  this.isProcessingMessage = true;
1796
1802
  while (this.messageQueue.length > 0) {
1797
1803
  const msg = this.messageQueue.shift();
1804
+ this.activeChatId = msg.chatId;
1798
1805
  await this.processMessage(msg.projectId, msg.chatId, msg.message, msg.referenceStashIds, msg.componentContext);
1799
1806
  }
1807
+ this.activeChatId = null;
1800
1808
  this.isProcessingMessage = false;
1801
1809
  }
1802
1810
  async processMessage(projectId, chatId, message, referenceStashIds, componentContext) {
@@ -1856,20 +1864,22 @@ ${sourceCode.substring(0, 3000)}
1856
1864
  const aiProcess = startAiProcess("chat", chatPrompt, this.projectPath, existingSessionId);
1857
1865
  let thinkingBuf = "";
1858
1866
  let textBuf = "";
1859
- const pendingMessages = [];
1860
1867
  const now = new Date().toISOString();
1861
- function flushThinking() {
1868
+ const save = (msg) => {
1869
+ this.persistence.saveChatMessage(projectId, chatId, msg);
1870
+ };
1871
+ const flushThinking = () => {
1862
1872
  if (!thinkingBuf)
1863
1873
  return;
1864
- pendingMessages.push({ id: crypto.randomUUID(), role: "assistant", content: thinkingBuf, type: "thinking", createdAt: now });
1874
+ save({ id: crypto.randomUUID(), role: "assistant", content: thinkingBuf, type: "thinking", createdAt: now });
1865
1875
  thinkingBuf = "";
1866
- }
1867
- function flushText() {
1876
+ };
1877
+ const flushText = () => {
1868
1878
  if (!textBuf)
1869
1879
  return;
1870
- pendingMessages.push({ id: crypto.randomUUID(), role: "assistant", content: textBuf, type: "text", createdAt: now });
1880
+ save({ id: crypto.randomUUID(), role: "assistant", content: textBuf, type: "text", createdAt: now });
1871
1881
  textBuf = "";
1872
- }
1882
+ };
1873
1883
  try {
1874
1884
  for await (const chunk of parseClaudeStream(aiProcess.process)) {
1875
1885
  if (chunk.type === "session_id" && chunk.sessionId) {
@@ -1903,7 +1913,7 @@ ${sourceCode.substring(0, 3000)}
1903
1913
  toolName = parsed.tool ?? "unknown";
1904
1914
  toolParams = parsed.input ?? {};
1905
1915
  } catch {}
1906
- pendingMessages.push({
1916
+ save({
1907
1917
  id: crypto.randomUUID(),
1908
1918
  role: "assistant",
1909
1919
  content: chunk.content,
@@ -1922,7 +1932,11 @@ ${sourceCode.substring(0, 3000)}
1922
1932
  toolParams,
1923
1933
  toolStatus: "running"
1924
1934
  });
1935
+ if (toolName.includes("stashes_generate") || toolName.includes("stashes_vary")) {
1936
+ this.startStashPoll(projectId, chatId);
1937
+ }
1925
1938
  } else if (chunk.type === "tool_result") {
1939
+ this.stopStashPoll();
1926
1940
  let toolResult = chunk.content;
1927
1941
  let isError = false;
1928
1942
  try {
@@ -1930,7 +1944,7 @@ ${sourceCode.substring(0, 3000)}
1930
1944
  toolResult = parsed.result ?? chunk.content;
1931
1945
  isError = !!parsed.is_error;
1932
1946
  } catch {}
1933
- pendingMessages.push({
1947
+ save({
1934
1948
  id: crypto.randomUUID(),
1935
1949
  role: "assistant",
1936
1950
  content: chunk.content,
@@ -1952,10 +1966,7 @@ ${sourceCode.substring(0, 3000)}
1952
1966
  await aiProcess.process.exited;
1953
1967
  flushThinking();
1954
1968
  flushText();
1955
- for (const msg of pendingMessages) {
1956
- this.persistence.saveChatMessage(projectId, chatId, msg);
1957
- }
1958
- this.syncStashesFromDisk(projectId);
1969
+ this.syncStashesFromDisk(projectId, chatId);
1959
1970
  } catch (err) {
1960
1971
  this.broadcast({
1961
1972
  type: "ai_stream",
@@ -1964,12 +1975,17 @@ ${sourceCode.substring(0, 3000)}
1964
1975
  source: "chat"
1965
1976
  });
1966
1977
  } finally {
1978
+ this.stopStashPoll();
1967
1979
  killAiProcess("chat");
1968
1980
  }
1969
1981
  }
1970
- syncStashesFromDisk(projectId) {
1982
+ syncStashesFromDisk(projectId, chatId) {
1971
1983
  const diskStashes = this.persistence.listStashes(projectId);
1972
1984
  for (const stash of diskStashes) {
1985
+ if (chatId && !stash.originChatId && !this.knownStashIds.has(stash.id)) {
1986
+ const updated = { ...stash, originChatId: chatId };
1987
+ this.persistence.saveStash(updated);
1988
+ }
1973
1989
  this.broadcast({
1974
1990
  type: "stash:status",
1975
1991
  stashId: stash.id,
@@ -1984,6 +2000,48 @@ ${sourceCode.substring(0, 3000)}
1984
2000
  screenshots: stash.screenshots?.length ? [...stash.screenshots] : undefined
1985
2001
  });
1986
2002
  }
2003
+ this.knownStashIds.add(stash.id);
2004
+ }
2005
+ }
2006
+ startStashPoll(projectId, chatId) {
2007
+ this.stopStashPoll();
2008
+ const lastStatus = new Map;
2009
+ for (const s of this.persistence.listStashes(projectId)) {
2010
+ this.knownStashIds.add(s.id);
2011
+ lastStatus.set(s.id, s.status);
2012
+ }
2013
+ this.stashPollTimer = setInterval(() => {
2014
+ const stashes = this.persistence.listStashes(projectId);
2015
+ for (const stash of stashes) {
2016
+ const prev = lastStatus.get(stash.id);
2017
+ if (!prev || prev !== stash.status) {
2018
+ if (chatId && !stash.originChatId && !prev) {
2019
+ this.persistence.saveStash({ ...stash, originChatId: chatId });
2020
+ }
2021
+ this.broadcast({
2022
+ type: "stash:status",
2023
+ stashId: stash.id,
2024
+ status: stash.status,
2025
+ number: stash.number
2026
+ });
2027
+ if (stash.screenshotUrl && prev !== stash.status) {
2028
+ this.broadcast({
2029
+ type: "stash:screenshot",
2030
+ stashId: stash.id,
2031
+ url: stash.screenshotUrl,
2032
+ screenshots: stash.screenshots?.length ? [...stash.screenshots] : undefined
2033
+ });
2034
+ }
2035
+ lastStatus.set(stash.id, stash.status);
2036
+ this.knownStashIds.add(stash.id);
2037
+ }
2038
+ }
2039
+ }, 2000);
2040
+ }
2041
+ stopStashPoll() {
2042
+ if (this.stashPollTimer) {
2043
+ clearInterval(this.stashPollTimer);
2044
+ this.stashPollTimer = null;
1987
2045
  }
1988
2046
  }
1989
2047
  progressToBroadcast(event) {
@@ -2110,6 +2168,10 @@ function createWebSocketHandler(projectPath, userDevPort, appProxyPort) {
2110
2168
  projectId: project.id,
2111
2169
  projectName: project.name
2112
2170
  }));
2171
+ const activeChatId = stashService.getActiveChatId();
2172
+ if (activeChatId) {
2173
+ ws.send(JSON.stringify({ type: "processing", chatId: activeChatId }));
2174
+ }
2113
2175
  },
2114
2176
  async message(ws, message) {
2115
2177
  const raw = typeof message === "string" ? message : new TextDecoder().decode(message);