@wrongstack/tui 0.250.0 → 0.255.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -338,8 +338,8 @@ interface RunTuiOptions {
338
338
  * visible bar without a round-trip. The initial value is loaded from
339
339
  * the config file before App mounts.
340
340
  */
341
- statuslineHiddenItems: Array<'todos' | 'plan' | 'fleet' | 'git' | 'elapsed' | 'context' | 'cost'>;
342
- setStatuslineHiddenItems: (items: Array<'todos' | 'plan' | 'fleet' | 'git' | 'elapsed' | 'context' | 'cost'>) => void;
341
+ statuslineHiddenItems: Array<'todos' | 'plan' | 'tasks' | 'fleet' | 'git' | 'elapsed' | 'context' | 'cost'>;
342
+ setStatuslineHiddenItems: (items: Array<'todos' | 'plan' | 'tasks' | 'fleet' | 'git' | 'elapsed' | 'context' | 'cost'>) => void;
343
343
  /**
344
344
  * Controller for the agents monitor overlay. App installs a dispatch-backed
345
345
  * setter on mount so the `/agents on|off` slash command can toggle the
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
- import { writeErr, resolveWstackPaths, loadGoal, InputBuilder, DefaultSessionRewinder, writeOut, formatTodosList, buildGoalPreamble, expectDefined as expectDefined$1, wstackGlobalRoot, shouldEnhance, enhanceUserPrompt, recentTextTurns, normalizedEqual, buildChildEnv } from '@wrongstack/core';
1
+ import { writeErr, resolveProjectDir, wstackGlobalRoot, GlobalMailbox, resolveWstackPaths, loadGoal, InputBuilder, DefaultSessionRewinder, writeOut, formatTodosList, buildGoalPreamble, expectDefined as expectDefined$1, shouldEnhance, enhanceUserPrompt, recentTextTurns, normalizedEqual, buildChildEnv } from '@wrongstack/core';
2
2
  export { buildGoalPreamble } from '@wrongstack/core';
3
3
  import { Box as Box$1, useInput, useStdin, useStdout, Text as Text$1, render, useApp, measureElement, Static } from 'ink';
4
+ import { randomUUID } from 'crypto';
4
5
  import * as path4 from 'path';
5
6
  import React5, { forwardRef, useState, useEffect, useMemo, memo, useRef, useCallback, useReducer, useLayoutEffect } from 'react';
6
7
  import * as fs2 from 'fs/promises';
@@ -86,6 +87,27 @@ var Box = forwardRef(function Box2({ borderColor, backgroundColor, ...rest }, re
86
87
  }
87
88
  );
88
89
  });
90
+ function useTokenCounterRefresh(tokenCounter, events) {
91
+ const [data, setData] = useState(
92
+ () => tokenCounter ? {
93
+ usage: tokenCounter.total(),
94
+ cost: tokenCounter.estimateCost(),
95
+ cacheStats: tokenCounter.cacheStats()
96
+ } : void 0
97
+ );
98
+ useEffect(() => {
99
+ if (!tokenCounter || !events) return;
100
+ const off = events.on("token.accounted", () => {
101
+ setData({
102
+ usage: tokenCounter.total(),
103
+ cost: tokenCounter.estimateCost(),
104
+ cacheStats: tokenCounter.cacheStats()
105
+ });
106
+ });
107
+ return off;
108
+ }, [tokenCounter, events]);
109
+ return data;
110
+ }
89
111
  var MODE_ICONS = {
90
112
  teach: "\u{1F9D1}\u200D\u{1F3EB}",
91
113
  brief: "\u26A1",
@@ -131,6 +153,7 @@ function StatusBar({
131
153
  processCount,
132
154
  context,
133
155
  hiddenItems,
156
+ events,
134
157
  eternalStage,
135
158
  goalSummary,
136
159
  indexState,
@@ -155,9 +178,10 @@ function StatusBar({
155
178
  const isCompact = termWidth < COMPACT_THRESHOLD;
156
179
  const isComfortable = termWidth >= COMFORTABLE_THRESHOLD;
157
180
  const hiddenSet = new Set(hiddenItems);
158
- const usage = tokenCounter?.total();
159
- const cost = tokenCounter?.estimateCost();
160
- const cache2 = tokenCounter?.cacheStats();
181
+ const tokenData = useTokenCounterRefresh(tokenCounter, events);
182
+ const usage = tokenData?.usage;
183
+ const cost = tokenData?.cost;
184
+ const cache2 = tokenData?.cacheStats;
161
185
  const [elapsedMs, setElapsedMs] = useState(startedAt ? Date.now() - startedAt : 0);
162
186
  useEffect(() => {
163
187
  if (startedAt == null) return;
@@ -555,7 +579,22 @@ function StatusBar({
555
579
  /* @__PURE__ */ jsxs(Text, { color: "cyan", children: [
556
580
  "\u{1F465} ",
557
581
  mailbox.onlineAgents,
558
- " online"
582
+ " agent",
583
+ mailbox.onlineAgents === 1 ? "" : "s",
584
+ mailbox.onlineClients.tui + mailbox.onlineClients.webui + mailbox.onlineClients.repl > 0 ? /* @__PURE__ */ jsxs(Text, { color: "green", children: [
585
+ mailbox.onlineClients.tui > 0 ? /* @__PURE__ */ jsxs(Text, { color: "green", children: [
586
+ " \xB7 \u{1F5A5} TUI",
587
+ mailbox.onlineClients.tui > 1 ? `\xD7${mailbox.onlineClients.tui}` : ""
588
+ ] }) : null,
589
+ mailbox.onlineClients.webui > 0 ? /* @__PURE__ */ jsxs(Text, { color: "green", children: [
590
+ " \xB7 \u{1F310} WebUI",
591
+ mailbox.onlineClients.webui > 1 ? `\xD7${mailbox.onlineClients.webui}` : ""
592
+ ] }) : null,
593
+ mailbox.onlineClients.repl > 0 ? /* @__PURE__ */ jsxs(Text, { color: "green", children: [
594
+ " \xB7 \u2328 REPL",
595
+ mailbox.onlineClients.repl > 1 ? `\xD7${mailbox.onlineClients.repl}` : ""
596
+ ] }) : null
597
+ ] }) : null
559
598
  ] }),
560
599
  mailbox.lastSubject ? /* @__PURE__ */ jsxs(Fragment, { children: [
561
600
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
@@ -4308,6 +4347,15 @@ function startHeapWatchdog(opts = {}) {
4308
4347
  }).catch(() => void 0);
4309
4348
  };
4310
4349
  const tick = () => {
4350
+ let userTimings = 0;
4351
+ try {
4352
+ userTimings = performance.getEntriesByType("mark").length + performance.getEntriesByType("measure").length;
4353
+ if (userTimings > 0) {
4354
+ performance.clearMarks();
4355
+ performance.clearMeasures();
4356
+ }
4357
+ } catch {
4358
+ }
4311
4359
  const s2 = takeHeapSample();
4312
4360
  if (s2.load >= criticalAt && criticalArmed) {
4313
4361
  criticalArmed = false;
@@ -4336,7 +4384,7 @@ function startHeapWatchdog(opts = {}) {
4336
4384
  extras = opts.collectStats?.() ?? {};
4337
4385
  } catch {
4338
4386
  }
4339
- append(JSON.stringify({ pid: process.pid, ...s2, ...extras }));
4387
+ append(JSON.stringify({ pid: process.pid, ...s2, userTimings, ...extras }));
4340
4388
  }
4341
4389
  };
4342
4390
  const timer = setInterval(tick, sampleEveryMs);
@@ -6931,6 +6979,7 @@ function useSessionEvents(events, dispatch, onClearHistory) {
6931
6979
  const offRewound = events.on("session.rewound", () => {
6932
6980
  dispatch({ type: "sessionRewound", toPromptIndex: 0 });
6933
6981
  dispatch({ type: "clearHistory" });
6982
+ dispatch({ type: "resetContextChip" });
6934
6983
  onClearHistory?.(dispatch);
6935
6984
  });
6936
6985
  return () => {
@@ -9148,7 +9197,8 @@ function App({
9148
9197
  }, [getLiveSessions]);
9149
9198
  const [mailboxStatus, setMailboxStatus] = useState({
9150
9199
  unread: 0,
9151
- onlineAgents: 0
9200
+ onlineAgents: 0,
9201
+ onlineClients: { tui: 0, webui: 0, repl: 0 }
9152
9202
  });
9153
9203
  useEffect(() => {
9154
9204
  const seenAgents = /* @__PURE__ */ new Set();
@@ -9174,11 +9224,25 @@ function App({
9174
9224
  if (p?.agentId) seenAgents.add(p.agentId);
9175
9225
  setMailboxStatus((prev) => ({ ...prev, onlineAgents: seenAgents.size }));
9176
9226
  });
9227
+ const unsub5 = events.onPattern("mailbox.sync_clients", (_e, payload) => {
9228
+ const p = payload;
9229
+ if (p) {
9230
+ setMailboxStatus((prev) => ({
9231
+ ...prev,
9232
+ onlineClients: {
9233
+ tui: p.tui ?? 0,
9234
+ webui: p.webui ?? 0,
9235
+ repl: p.repl ?? 0
9236
+ }
9237
+ }));
9238
+ }
9239
+ });
9177
9240
  return () => {
9178
9241
  unsub1();
9179
9242
  unsub2();
9180
9243
  unsub3();
9181
9244
  unsub4();
9245
+ unsub5();
9182
9246
  };
9183
9247
  }, [events]);
9184
9248
  const [mailboxPanelOpen, setMailboxPanelOpen] = useState(false);
@@ -12248,6 +12312,7 @@ User message:
12248
12312
  subagentCount: Object.keys(state.fleet).length,
12249
12313
  processCount: getProcessRegistry().activeCount,
12250
12314
  hiddenItems,
12315
+ events,
12251
12316
  eternalStage: state.eternalStage,
12252
12317
  goalSummary: state.goalSummary,
12253
12318
  indexState,
@@ -12523,6 +12588,7 @@ async function runTui(opts) {
12523
12588
  const cleanup = () => {
12524
12589
  if (cleaned) return;
12525
12590
  cleaned = true;
12591
+ unregisterTuiClient();
12526
12592
  unsilenceTerminal();
12527
12593
  try {
12528
12594
  stopTitle();
@@ -12549,6 +12615,64 @@ async function runTui(opts) {
12549
12615
  }
12550
12616
  process.off("exit", exitHandler);
12551
12617
  };
12618
+ let clientHeartbeatTimer = null;
12619
+ let clientSyncTimer = null;
12620
+ const CLIENT_HEARTBEAT_MS = 15e3;
12621
+ const CLIENT_SYNC_MS = 3e4;
12622
+ const registerTuiClient = async () => {
12623
+ if (!opts.projectRoot) return null;
12624
+ try {
12625
+ const projectDir = resolveProjectDir(opts.projectRoot, wstackGlobalRoot());
12626
+ const mailbox = new GlobalMailbox(projectDir, opts.events);
12627
+ const clientId = `tui@${randomUUID().slice(0, 8)}`;
12628
+ await mailbox.registerClient({
12629
+ clientId,
12630
+ sessionId: opts.projectRoot,
12631
+ name: `TUI [${path4.basename(opts.projectRoot)}]`,
12632
+ source: "tui",
12633
+ pid: process.pid
12634
+ });
12635
+ clientHeartbeatTimer = setInterval(() => {
12636
+ mailbox.clientHeartbeat({ clientId }).catch(() => {
12637
+ });
12638
+ }, CLIENT_HEARTBEAT_MS);
12639
+ clientHeartbeatTimer.unref();
12640
+ const syncClients = async () => {
12641
+ try {
12642
+ const statuses = await mailbox.getClientStatuses();
12643
+ const counts = { tui: 0, webui: 0, repl: 0 };
12644
+ for (const s2 of statuses) {
12645
+ if (s2.online && s2.source in counts) {
12646
+ counts[s2.source]++;
12647
+ }
12648
+ }
12649
+ opts.events.emitCustom("mailbox.sync_clients", counts);
12650
+ } catch {
12651
+ }
12652
+ };
12653
+ setTimeout(() => {
12654
+ void syncClients();
12655
+ }, 5e3);
12656
+ clientSyncTimer = setInterval(() => {
12657
+ void syncClients();
12658
+ }, CLIENT_SYNC_MS);
12659
+ clientSyncTimer.unref();
12660
+ return clientId;
12661
+ } catch {
12662
+ return null;
12663
+ }
12664
+ };
12665
+ const unregisterTuiClient = () => {
12666
+ if (clientHeartbeatTimer) {
12667
+ clearInterval(clientHeartbeatTimer);
12668
+ clientHeartbeatTimer = null;
12669
+ }
12670
+ if (clientSyncTimer) {
12671
+ clearInterval(clientSyncTimer);
12672
+ clientSyncTimer = null;
12673
+ }
12674
+ };
12675
+ registerTuiClient();
12552
12676
  return new Promise((resolve) => {
12553
12677
  let exitCode = 0;
12554
12678
  let hardExitTimer = null;