@thoth-agents/pi-thoth-theme 0.5.0 → 0.6.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 (2) hide show
  1. package/dist/index.ts +202 -24
  2. package/package.json +2 -2
package/dist/index.ts CHANGED
@@ -1745,43 +1745,71 @@ function decorateEditor(editor, deps) {
1745
1745
  import { truncateToWidth as truncateToWidth7, visibleWidth as visibleWidth6 } from "@earendil-works/pi-tui";
1746
1746
  function createWorkingState(requestRender) {
1747
1747
  let startedAt;
1748
+ let pendingBackground = 0;
1748
1749
  let ticker;
1749
1750
  let disposed = false;
1750
1751
  function stop() {
1751
1752
  if (ticker !== void 0) clearInterval(ticker);
1752
1753
  ticker = void 0;
1753
- startedAt = void 0;
1754
+ }
1755
+ function syncTicker() {
1756
+ if (startedAt === void 0 && pendingBackground === 0) {
1757
+ stop();
1758
+ } else if (ticker === void 0) {
1759
+ ticker = setInterval(requestRender, 50);
1760
+ ticker.unref?.();
1761
+ }
1762
+ requestRender();
1754
1763
  }
1755
1764
  return {
1756
1765
  get isWorking() {
1757
- return startedAt !== void 0;
1766
+ return startedAt !== void 0 || pendingBackground > 0;
1758
1767
  },
1759
1768
  start() {
1760
1769
  if (disposed) return;
1761
- stop();
1762
1770
  startedAt = Date.now();
1763
- ticker = setInterval(requestRender, 50);
1764
- ticker.unref?.();
1765
- requestRender();
1771
+ syncTicker();
1766
1772
  },
1767
1773
  end() {
1768
1774
  if (disposed) return;
1769
- stop();
1770
- requestRender();
1775
+ startedAt = void 0;
1776
+ syncTicker();
1777
+ },
1778
+ setPendingBackground(count) {
1779
+ if (disposed || pendingBackground === count) return;
1780
+ pendingBackground = count;
1781
+ syncTicker();
1771
1782
  },
1772
1783
  status(indicator, width, styleMuted = (text) => text, frame, mode = "nerd") {
1773
1784
  const ready = `${icon("ready", mode)} ready`;
1774
1785
  const separator2 = icon("separator", mode);
1786
+ const now = frame?.now ?? Date.now();
1787
+ const fallback = () => {
1788
+ if (pendingBackground === 0)
1789
+ return styleMuted(truncateToWidth7(ready, width, ""));
1790
+ const glyphs = frames("workingFrames", mode);
1791
+ const glyph = glyphs[Math.floor(now / 200) % glyphs.length];
1792
+ const label2 = renderWorkingIndicator(
1793
+ `${glyph} working${icon("ellipsis", mode)}`,
1794
+ frame ?? createBreathingFrame(now),
1795
+ styleMuted,
1796
+ glyphs
1797
+ );
1798
+ return truncateToWidth7(
1799
+ `${label2}${styleMuted(` ${separator2} ${pendingBackground} bg`)}`,
1800
+ width,
1801
+ ""
1802
+ );
1803
+ };
1775
1804
  if (!indicator || typeof indicator !== "object" || !("renderInBorder" in indicator) || typeof indicator.renderInBorder !== "function") {
1776
- return styleMuted(truncateToWidth7(ready, width, ""));
1805
+ return fallback();
1777
1806
  }
1778
1807
  const working = "kind" in indicator && indicator.kind === "working";
1779
- const now = frame?.now ?? Date.now();
1780
1808
  const elapsed = working && startedAt !== void 0 ? ` ${separator2} ${formatDuration(Math.floor((now - startedAt) / 1e3) * 1e3)}` : "";
1781
1809
  const nativeWidth = Math.max(1, width - visibleWidth6(elapsed));
1782
1810
  const native = indicator.renderInBorder(nativeWidth);
1783
1811
  if (typeof native !== "string" || visibleWidth6(native) === 0) {
1784
- return styleMuted(truncateToWidth7(ready, width, ""));
1812
+ return fallback();
1785
1813
  }
1786
1814
  const fitted = truncateToWidth7(native, nativeWidth, "");
1787
1815
  const label = working && startedAt !== void 0 ? renderWorkingIndicator(
@@ -1798,11 +1826,107 @@ function createWorkingState(requestRender) {
1798
1826
  },
1799
1827
  dispose() {
1800
1828
  disposed = true;
1829
+ startedAt = void 0;
1830
+ pendingBackground = 0;
1801
1831
  stop();
1802
1832
  }
1803
1833
  };
1804
1834
  }
1805
1835
 
1836
+ // src/status-line/orca-child-hold.ts
1837
+ var memoryKey = /* @__PURE__ */ Symbol.for("@thoth-agents/pi-thoth-theme/orca-child-holds");
1838
+ function createOrcaChildHold(events) {
1839
+ const processState = globalThis;
1840
+ processState[memoryKey] ??= /* @__PURE__ */ new Map();
1841
+ const sessions = processState[memoryKey];
1842
+ let announced = /* @__PURE__ */ new Set();
1843
+ let subagents = [];
1844
+ let background = [];
1845
+ let timer;
1846
+ let disposed = false;
1847
+ function reconcile(prefix, tasks) {
1848
+ const live = /* @__PURE__ */ new Set();
1849
+ const byId = /* @__PURE__ */ new Map();
1850
+ for (const task of tasks) {
1851
+ if (disposed) return;
1852
+ const id = `${prefix}${task.id}`;
1853
+ if (!byId.has(id)) byId.set(id, task);
1854
+ if (!task.live) continue;
1855
+ live.add(id);
1856
+ if (announced.has(id)) continue;
1857
+ announced.add(id);
1858
+ events.emit("task:subagent:lifecycle", {
1859
+ id,
1860
+ status: "started",
1861
+ agent: task.agent,
1862
+ ...task.description ? { description: task.description } : {}
1863
+ });
1864
+ }
1865
+ for (const id of announced) {
1866
+ if (disposed) return;
1867
+ if (!id.startsWith(prefix) || live.has(id)) continue;
1868
+ announced.delete(id);
1869
+ const status = byId.get(id)?.status;
1870
+ events.emit("task:subagent:lifecycle", {
1871
+ id,
1872
+ status: status === "failed" || status === "timed_out" ? "failed" : status === "cancelled" || status === "interrupted" ? "aborted" : "completed"
1873
+ });
1874
+ }
1875
+ }
1876
+ return {
1877
+ startSession(sessionId) {
1878
+ if (disposed) return;
1879
+ if (timer !== void 0) clearTimeout(timer);
1880
+ announced = sessions.get(sessionId) ?? /* @__PURE__ */ new Set();
1881
+ sessions.set(sessionId, announced);
1882
+ subagents = [];
1883
+ background = [];
1884
+ timer = setTimeout(() => {
1885
+ timer = void 0;
1886
+ if (disposed) return;
1887
+ reconcile("thoth:subagent:", subagents);
1888
+ reconcile("thoth:bg:", background);
1889
+ }, 0);
1890
+ timer.unref?.();
1891
+ },
1892
+ updateSubagents(data) {
1893
+ if (disposed) return;
1894
+ subagents = [
1895
+ ...data.tasks.map((task) => ({
1896
+ id: task.id,
1897
+ live: task.mode === "background" && (task.status === "queued" || task.status === "running" || task.status === "stopping"),
1898
+ status: task.status,
1899
+ agent: task.agent,
1900
+ description: task.displayName
1901
+ })),
1902
+ ...data.history.map((task) => ({
1903
+ id: task.id,
1904
+ live: false,
1905
+ status: task.status,
1906
+ agent: task.agent
1907
+ }))
1908
+ ];
1909
+ if (timer === void 0) reconcile("thoth:subagent:", subagents);
1910
+ },
1911
+ updateBackground(data) {
1912
+ if (disposed) return;
1913
+ background = data.tasks.map((task) => ({
1914
+ id: task.id,
1915
+ live: task.status === "running",
1916
+ status: task.status,
1917
+ agent: task.kind === "command_watch" ? "watch" : "process",
1918
+ description: task.name
1919
+ }));
1920
+ if (timer === void 0) reconcile("thoth:bg:", background);
1921
+ },
1922
+ dispose() {
1923
+ disposed = true;
1924
+ if (timer !== void 0) clearTimeout(timer);
1925
+ timer = void 0;
1926
+ }
1927
+ };
1928
+ }
1929
+
1806
1930
  // src/status-line/throughput.ts
1807
1931
  function createThroughputTracker() {
1808
1932
  let pending;
@@ -1946,6 +2070,7 @@ function registerStatusLine(pi, ctx, config) {
1946
2070
  requestRender();
1947
2071
  };
1948
2072
  const working = createWorkingState(invalidateRender);
2073
+ const orcaChildHold = createOrcaChildHold(pi.events);
1949
2074
  const editorDecorations = /* @__PURE__ */ new Set();
1950
2075
  const refreshSession = () => {
1951
2076
  if (disposed) return;
@@ -1953,7 +2078,12 @@ function registerStatusLine(pi, ctx, config) {
1953
2078
  invalidateRender();
1954
2079
  };
1955
2080
  let offSubagentUsage;
2081
+ let offBackgroundWork = [];
1956
2082
  unsubs.push(() => offSubagentUsage?.());
2083
+ unsubs.push(() => {
2084
+ for (const off of offBackgroundWork) off();
2085
+ offBackgroundWork = [];
2086
+ });
1957
2087
  if (footerData?.onBranchChange) {
1958
2088
  unsubs.push(footerData.onBranchChange(invalidateRender));
1959
2089
  }
@@ -1974,7 +2104,50 @@ function registerStatusLine(pi, ctx, config) {
1974
2104
  data: {}
1975
2105
  });
1976
2106
  };
2107
+ const subscribeAndRequestBackgroundWork = () => {
2108
+ const sessionId = ctx.sessionManager.getSessionId();
2109
+ for (const off of offBackgroundWork) off();
2110
+ orcaChildHold.startSession(sessionId);
2111
+ let subagentPending = 0;
2112
+ let backgroundPending = 0;
2113
+ if (inputBoxEnabled) working.setPendingBackground(0);
2114
+ offBackgroundWork = [
2115
+ subscribe(pi.events, SUBAGENTS_STATE_CHANNEL, {
2116
+ sessionId,
2117
+ onSnapshot: ({ data }) => {
2118
+ if (disposed) return;
2119
+ orcaChildHold.updateSubagents(data);
2120
+ subagentPending = data.tasks.filter(
2121
+ (task) => task.mode === "background" && (task.status === "queued" || task.status === "running" || task.status === "stopping")
2122
+ ).length;
2123
+ if (inputBoxEnabled)
2124
+ working.setPendingBackground(subagentPending + backgroundPending);
2125
+ }
2126
+ }),
2127
+ subscribe(pi.events, BACKGROUND_STATE_CHANNEL, {
2128
+ sessionId,
2129
+ onSnapshot: ({ data }) => {
2130
+ if (disposed) return;
2131
+ orcaChildHold.updateBackground(data);
2132
+ backgroundPending = data.counts.running;
2133
+ if (inputBoxEnabled)
2134
+ working.setPendingBackground(subagentPending + backgroundPending);
2135
+ }
2136
+ })
2137
+ ];
2138
+ for (const channel of [
2139
+ SUBAGENTS_STATE_REQUEST,
2140
+ BACKGROUND_STATE_REQUEST
2141
+ ]) {
2142
+ request(pi.events, channel, {
2143
+ source: "@thoth-agents/pi-thoth-theme",
2144
+ sessionId,
2145
+ data: {}
2146
+ });
2147
+ }
2148
+ };
1977
2149
  if (pi?.on) {
2150
+ unsubs.push(pi.on("session_shutdown", dispose));
1978
2151
  unsubs.push(
1979
2152
  pi.on("session_start", () => {
1980
2153
  if (disposed) return;
@@ -1983,6 +2156,7 @@ function registerStatusLine(pi, ctx, config) {
1983
2156
  if (inputBoxEnabled) working.end();
1984
2157
  refreshSession();
1985
2158
  subscribeAndRequestSubagentUsage();
2159
+ subscribeAndRequestBackgroundWork();
1986
2160
  })
1987
2161
  );
1988
2162
  if (inputBoxEnabled) {
@@ -2008,6 +2182,22 @@ function registerStatusLine(pi, ctx, config) {
2008
2182
  unsubs.push(pi.on("thinking_level_select", invalidateRender));
2009
2183
  }
2010
2184
  subscribeAndRequestSubagentUsage();
2185
+ subscribeAndRequestBackgroundWork();
2186
+ function dispose() {
2187
+ if (disposed) return;
2188
+ disposed = true;
2189
+ working.dispose();
2190
+ orcaChildHold.dispose();
2191
+ for (const decoration of editorDecorations) decoration.dispose();
2192
+ editorDecorations.clear();
2193
+ for (const unsub of unsubs) {
2194
+ try {
2195
+ unsub();
2196
+ } catch {
2197
+ }
2198
+ }
2199
+ unsubs.length = 0;
2200
+ }
2011
2201
  return {
2012
2202
  getStatusSnapshot,
2013
2203
  render(width) {
@@ -2041,19 +2231,7 @@ function registerStatusLine(pi, ctx, config) {
2041
2231
  return cachedLines;
2042
2232
  },
2043
2233
  invalidate: invalidateRender,
2044
- dispose() {
2045
- disposed = true;
2046
- working.dispose();
2047
- for (const decoration of editorDecorations) decoration.dispose();
2048
- editorDecorations.clear();
2049
- for (const unsub of unsubs) {
2050
- try {
2051
- unsub();
2052
- } catch {
2053
- }
2054
- }
2055
- unsubs.length = 0;
2056
- }
2234
+ dispose
2057
2235
  };
2058
2236
  });
2059
2237
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thoth-agents/pi-thoth-theme",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "private": false,
5
5
  "description": "Modular Thoth theme, status line, tool styling, and welcome header for Pi.",
6
6
  "license": "MIT",
@@ -50,7 +50,7 @@
50
50
  "@earendil-works/pi-tui": "1.0.2",
51
51
  "@earendil-works/pi-ai": "1.0.2",
52
52
  "@rolldown/binding-wasm32-wasi": "^1.2.4",
53
- "@thoth-agents/pi-core": "^0.4.0"
53
+ "@thoth-agents/pi-core": "^0.5.0"
54
54
  },
55
55
  "upstream": {
56
56
  "repository": "https://github.com/QuangThai/pi-omp-theme",