@wrongstack/tui 0.8.5 → 0.9.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.js CHANGED
@@ -162,156 +162,37 @@ function FilePicker({ query, matches, selected }) {
162
162
  function highlight(path3, query) {
163
163
  return path3;
164
164
  }
165
- var STATUS_ICON = {
166
- idle: { icon: "\u25CB", color: "gray" },
167
- running: { icon: "\u25CF", color: "green" },
168
- success: { icon: "\u2713", color: "green" },
169
- failed: { icon: "\u2717", color: "red" },
170
- timeout: { icon: "\u23F1", color: "yellow" },
171
- stopped: { icon: "\u2298", color: "yellow" }
172
- };
173
- function fmtCost(n) {
174
- if (n === 0) return "\u2014";
175
- return `$${n.toFixed(3)}`;
176
- }
177
- function fmtCount(n) {
178
- if (n === 0) return "\u2014";
179
- return String(n);
180
- }
181
- function fmtDuration(ms) {
182
- if (ms < 1e3) return `${ms}ms`;
183
- return `${(ms / 1e3).toFixed(1)}s`;
184
- }
185
- function fmtBytes(n) {
186
- if (n < 1024) return `${n}B`;
187
- return `${(n / 1024).toFixed(1)}KB`;
188
- }
189
- function fmtRecentTool(tool) {
190
- const status = tool.ok === false ? "fail" : "ok";
191
- const name = tool.name.length > 24 ? `${tool.name.slice(0, 23)}...` : tool.name;
192
- const parts = [status, name];
193
- if (typeof tool.durationMs === "number") parts.push(fmtDuration(tool.durationMs));
194
- if (typeof tool.outputBytes === "number" && tool.outputBytes > 0) parts.push(fmtBytes(tool.outputBytes));
195
- if (typeof tool.outputLines === "number" && tool.outputLines > 0) parts.push(`${tool.outputLines}L`);
196
- return parts.join(" ");
197
- }
198
- function fmtRecentMessage(message) {
199
- const text = message.text.replace(/\s+/g, " ");
200
- return text.length > 80 ? `${text.slice(0, 79)}...` : text;
201
- }
202
- function fmtModel(provider, model) {
203
- if (!provider && !model) return "";
204
- const p = provider ?? "";
205
- const m = model ?? "";
206
- return p && m ? `${p}/${m}` : p || m;
207
- }
208
- function resolveName(entry, roster) {
209
- const rosterEntry = roster?.[entry.id];
210
- if (rosterEntry) return rosterEntry.name;
211
- return entry.name;
212
- }
213
165
  function FleetPanel({ entries, totalCost, roster }) {
214
166
  const list = Object.values(entries);
215
167
  if (list.length === 0) return null;
216
- const sorted = [...list].sort((a, b) => {
217
- const order = { running: 0, success: 1, failed: 2, timeout: 3, stopped: 4, idle: 5 };
218
- const ao = order[a.status] ?? 9;
219
- const bo = order[b.status] ?? 9;
220
- if (ao !== bo) return ao - bo;
221
- return b.lastEventAt - a.lastEventAt;
222
- });
223
- const runningCount = list.filter((e) => e.status === "running").length;
224
- const totalLabel = totalCost > 0 ? `$${totalCost.toFixed(3)} \xB7 ${runningCount} active` : `${runningCount} active`;
225
- return /* @__PURE__ */ jsxs(
226
- Box,
227
- {
228
- flexDirection: "column",
229
- paddingX: 1,
230
- borderStyle: "single",
231
- borderTop: false,
232
- borderBottom: false,
233
- borderLeft: false,
234
- borderRight: false,
235
- children: [
236
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 2, children: [
237
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Fleet" }),
238
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
239
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
240
- list.length,
241
- " agent",
242
- list.length === 1 ? "" : "s"
243
- ] }),
244
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
245
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: totalLabel })
246
- ] }),
247
- sorted.map((entry) => {
248
- const si = STATUS_ICON[entry.status];
249
- const modelTag = fmtModel(entry.provider, entry.model);
250
- const name = resolveName(entry, roster);
251
- const recentTools = (entry.recentTools ?? []).slice(-2).map(fmtRecentTool).join(" | ");
252
- const recentMessages = (entry.recentMessages ?? []).slice(-2).map(fmtRecentMessage);
253
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
254
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
255
- /* @__PURE__ */ jsx(Text, { color: si.color, children: si.icon }),
256
- /* @__PURE__ */ jsx(Text, { children: name.slice(0, 16).padEnd(16) }),
257
- modelTag ? /* @__PURE__ */ jsxs(Fragment, { children: [
258
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
259
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: modelTag })
260
- ] }) : null,
261
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
262
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
263
- fmtCount(entry.iterations).padStart(3),
264
- "it"
265
- ] }),
266
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
267
- fmtCount(entry.toolCalls).padStart(3),
268
- "tc"
269
- ] }),
270
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
271
- /* @__PURE__ */ jsx(Text, { color: "yellow", children: fmtCost(entry.cost) })
272
- ] }),
273
- entry.status === "running" && entry.currentTool ? /* @__PURE__ */ jsxs(Box, { paddingLeft: 2, children: [
274
- /* @__PURE__ */ jsxs(Text, { color: "cyan", children: [
275
- "\u2192 ",
276
- entry.currentTool.name
277
- ] }),
278
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
279
- " (",
280
- Math.max(0, Date.now() - entry.currentTool.startedAt),
281
- "ms)"
282
- ] })
283
- ] }) : null,
284
- recentTools ? /* @__PURE__ */ jsx(Box, { paddingLeft: 2, children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
285
- "tools: ",
286
- recentTools
287
- ] }) }) : null,
288
- recentMessages.map((message, index) => /* @__PURE__ */ jsx(Box, { paddingLeft: 2, children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
289
- "msg: ",
290
- message
291
- ] }) }, `${entry.id}-msg-${index}-${message}`)),
292
- entry.budgetWarning ? /* @__PURE__ */ jsx(Box, { paddingLeft: 2, children: /* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
293
- "\u26A1 hitting ",
294
- entry.budgetWarning.kind,
295
- " limit (",
296
- entry.budgetWarning.used,
297
- "/",
298
- entry.budgetWarning.limit,
299
- ") \u2014 extending"
300
- ] }) }) : null,
301
- entry.status === "running" && entry.streamingText ? /* @__PURE__ */ jsx(Box, { paddingLeft: 2, children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
302
- ">",
303
- " ",
304
- entry.streamingText.slice(-80)
305
- ] }) }) : null,
306
- entry.transcriptPath ? /* @__PURE__ */ jsx(Box, { paddingLeft: 2, children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
307
- "log: ",
308
- entry.transcriptPath
309
- ] }) }) : null
310
- ] }, entry.id);
311
- })
312
- ]
313
- }
314
- );
168
+ const running = list.filter((e) => e.status === "running");
169
+ const runningCount = running.length;
170
+ const costLabel = totalCost > 0 ? ` \xB7 $${totalCost.toFixed(3)}` : "";
171
+ const summaryLine = runningCount > 0 ? `${runningCount} running${costLabel}` : `idle${costLabel}`;
172
+ const shown = running.slice(0, 3);
173
+ const overflow = running.length > 3 ? running.length - 3 : 0;
174
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
175
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
176
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u26A1 Fleet" }),
177
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
178
+ /* @__PURE__ */ jsx(Text, { children: summaryLine })
179
+ ] }),
180
+ shown.map((entry) => {
181
+ const name = roster?.[entry.id]?.name ?? entry.name;
182
+ const tool = entry.currentTool?.name ?? "\u2014";
183
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
184
+ /* @__PURE__ */ jsx(Text, { color: "green", children: "\u25CF" }),
185
+ /* @__PURE__ */ jsx(Text, { children: name.slice(0, 12).padEnd(12) }),
186
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2192" }),
187
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: tool })
188
+ ] }, entry.id);
189
+ }),
190
+ overflow > 0 ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
191
+ " +",
192
+ overflow,
193
+ " more running"
194
+ ] }) : null
195
+ ] });
315
196
  }
316
197
  function StatusBar({
317
198
  model,
@@ -721,7 +602,7 @@ function fmtTokens(n) {
721
602
  if (n < 1e6) return `${(n / 1e3).toFixed(1)}k`;
722
603
  return `${(n / 1e6).toFixed(1)}M`;
723
604
  }
724
- function fmtCost2(n) {
605
+ function fmtCost(n) {
725
606
  if (n === 0) return "\u2014";
726
607
  return `$${n.toFixed(3)}`;
727
608
  }
@@ -832,7 +713,7 @@ function FleetMonitor({
832
713
  /* @__PURE__ */ jsx(Text, { color: s2.color, children: e.status.padEnd(10) }),
833
714
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: `L${e.iterations} ${e.toolCalls}t`.padEnd(8) }),
834
715
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: elapsed.padEnd(8).slice(0, 8) }),
835
- /* @__PURE__ */ jsx(Text, { color: "yellow", children: fmtCost2(e.cost) }),
716
+ /* @__PURE__ */ jsx(Text, { color: "yellow", children: fmtCost(e.cost) }),
836
717
  e.extensions && e.extensions > 0 ? /* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
837
718
  " \u26A1\xD7",
838
719
  e.extensions
@@ -865,6 +746,20 @@ var STATUS2 = {
865
746
  function isTerminal(status) {
866
747
  return status === "success" || status === "failed" || status === "timeout" || status === "stopped";
867
748
  }
749
+ var IDLE_HIDE_MS = 6e4;
750
+ function selectLiveAgents(all, now, idleHideMs = IDLE_HIDE_MS) {
751
+ const visible = all.filter((e) => {
752
+ if (isTerminal(e.status)) return false;
753
+ if (e.status === "running") return true;
754
+ return now - e.lastEventAt < idleHideMs;
755
+ });
756
+ return visible.sort((a, b) => {
757
+ if (a.status === "running" && b.status !== "running") return -1;
758
+ if (a.status !== "running" && b.status === "running") return 1;
759
+ if (a.status === "running") return a.startedAt - b.startedAt;
760
+ return b.lastEventAt - a.lastEventAt;
761
+ });
762
+ }
868
763
  function fmtTokens2(n) {
869
764
  if (n < 1e3) return String(n);
870
765
  if (n < 1e6) return `${(n / 1e3).toFixed(1)}k`;
@@ -882,15 +777,14 @@ function AgentsMonitor({
882
777
  nowTick
883
778
  }) {
884
779
  const all = Object.values(entries);
885
- const live = all.filter((e) => !isTerminal(e.status));
780
+ const live = selectLiveAgents(all, nowTick);
886
781
  const running = live.filter((e) => e.status === "running").length;
887
782
  const totalDone = all.filter((e) => e.status === "success").length;
888
783
  const totalFailed = all.filter((e) => e.status === "failed" || e.status === "timeout").length;
889
- const ordered = [...live].sort((a, b) => {
890
- if (a.status === "running" && b.status !== "running") return -1;
891
- if (a.status !== "running" && b.status === "running") return 1;
892
- return a.startedAt - b.startedAt;
893
- });
784
+ const hiddenIdle = all.filter(
785
+ (e) => e.status === "idle" && nowTick - e.lastEventAt >= IDLE_HIDE_MS
786
+ ).length;
787
+ const ordered = live;
894
788
  const shown = ordered.slice(0, 8);
895
789
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 1, children: [
896
790
  /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
@@ -927,7 +821,12 @@ function AgentsMonitor({
927
821
  /* @__PURE__ */ jsxs(Text, { color: "green", children: [
928
822
  "$",
929
823
  totalCost.toFixed(3)
930
- ] })
824
+ ] }),
825
+ hiddenIdle > 0 ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
826
+ "\xB7 ",
827
+ hiddenIdle,
828
+ " idle hidden"
829
+ ] }) : null
931
830
  ] }),
932
831
  shown.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No live agents \u2014 spawn with /spawn or /fleet dispatch." }) : null,
933
832
  shown.map((e) => {
@@ -1151,18 +1050,18 @@ function PhasePanel({ phases, runningPhaseIds, nowTick }) {
1151
1050
  ] }),
1152
1051
  list.map((phase, i) => {
1153
1052
  const phaseKey = Object.keys(phases).find((k) => phases[k] === phase) ?? String(i);
1154
- const st = s(phase.status);
1053
+ const st2 = s(phase.status);
1155
1054
  const progress = phase.totalTasks > 0 ? `${phase.completedTasks}/${phase.totalTasks}` : "";
1156
1055
  const elapsed = phase.startedAt ? fmtElapsed3(nowTick - phase.startedAt) : "";
1157
1056
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
1158
- /* @__PURE__ */ jsx(Text, { color: st.color, children: st.icon }),
1057
+ /* @__PURE__ */ jsx(Text, { color: st2.color, children: st2.icon }),
1159
1058
  /* @__PURE__ */ jsx(Text, { children: phase.name.slice(0, 14).padEnd(14) }),
1160
- /* @__PURE__ */ jsx(Text, { color: st.color, dimColor: true, children: st.icon === "\u25CF" ? phase.status : "" }),
1059
+ /* @__PURE__ */ jsx(Text, { color: st2.color, dimColor: true, children: st2.icon === "\u25CF" ? phase.status : "" }),
1161
1060
  progress ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1162
1061
  " ",
1163
1062
  progress
1164
1063
  ] }) : null,
1165
- elapsed && st.icon === "\u25CF" ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1064
+ elapsed && st2.icon === "\u25CF" ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1166
1065
  " ",
1167
1066
  elapsed
1168
1067
  ] }) : null
@@ -1172,6 +1071,204 @@ function PhasePanel({ phases, runningPhaseIds, nowTick }) {
1172
1071
  }
1173
1072
  );
1174
1073
  }
1074
+ var fmtElapsed4 = (ms) => {
1075
+ const s2 = Math.floor(ms / 1e3);
1076
+ const m = Math.floor(s2 / 60);
1077
+ const h = Math.floor(m / 60);
1078
+ if (h > 0) return `${h}:${String(m % 60).padStart(2, "0")}:${String(s2 % 60).padStart(2, "0")}`;
1079
+ if (m > 0) return `${m}:${String(s2 % 60).padStart(2, "0")}`;
1080
+ return `${s2}s`;
1081
+ };
1082
+ var STATUS4 = {
1083
+ allocating: { icon: "\u25CB", color: "gray" },
1084
+ active: { icon: "\u25CF", color: "yellow" },
1085
+ committing: { icon: "\u25D0", color: "cyan" },
1086
+ merging: { icon: "\u21E1", color: "blue" },
1087
+ merged: { icon: "\u2713", color: "green" },
1088
+ "needs-review": { icon: "\u26A0", color: "magenta" },
1089
+ failed: { icon: "\u2717", color: "red" }
1090
+ };
1091
+ function st(status) {
1092
+ return STATUS4[status] ?? { icon: "?", color: "white" };
1093
+ }
1094
+ function WorktreePanel({
1095
+ worktrees,
1096
+ nowTick
1097
+ }) {
1098
+ const list = Object.values(worktrees);
1099
+ if (list.length === 0) return null;
1100
+ const active = list.filter((w) => w.status === "active" || w.status === "committing" || w.status === "merging").length;
1101
+ const merged = list.filter((w) => w.status === "merged").length;
1102
+ const failed = list.filter((w) => w.status === "failed" || w.status === "needs-review").length;
1103
+ return /* @__PURE__ */ jsxs(
1104
+ Box,
1105
+ {
1106
+ flexDirection: "column",
1107
+ paddingX: 1,
1108
+ borderStyle: "single",
1109
+ borderTop: false,
1110
+ borderBottom: false,
1111
+ borderLeft: false,
1112
+ borderRight: false,
1113
+ children: [
1114
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 2, children: [
1115
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Worktrees" }),
1116
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
1117
+ /* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
1118
+ "\u25B6",
1119
+ active
1120
+ ] }),
1121
+ /* @__PURE__ */ jsxs(Text, { color: "green", children: [
1122
+ "\u2713",
1123
+ merged
1124
+ ] }),
1125
+ failed > 0 ? /* @__PURE__ */ jsxs(Text, { color: "red", children: [
1126
+ "\u2717",
1127
+ failed
1128
+ ] }) : null,
1129
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1130
+ "\xB7 ",
1131
+ list.length,
1132
+ " total \xB7 Ctrl+W for details"
1133
+ ] })
1134
+ ] }),
1135
+ list.map((w) => {
1136
+ const s2 = st(w.status);
1137
+ const conflict = w.status === "needs-review";
1138
+ const elapsed = w.allocatedAt ? fmtElapsed4(nowTick - w.allocatedAt) : "";
1139
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
1140
+ /* @__PURE__ */ jsx(Text, { color: s2.color, children: s2.icon }),
1141
+ /* @__PURE__ */ jsx(Text, { children: w.branch.replace(/^wstack\/ap\//, "").slice(0, 18).padEnd(18) }),
1142
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: w.ownerLabel.slice(0, 12) }),
1143
+ conflict ? /* @__PURE__ */ jsx(Text, { color: "magenta", children: " CONFLICT" }) : /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1144
+ " +",
1145
+ w.insertions,
1146
+ "/-",
1147
+ w.deletions,
1148
+ " ",
1149
+ w.files,
1150
+ "f"
1151
+ ] }),
1152
+ elapsed && (w.status === "active" || w.status === "committing") ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1153
+ " ",
1154
+ elapsed
1155
+ ] }) : null
1156
+ ] }, w.branch);
1157
+ })
1158
+ ]
1159
+ }
1160
+ );
1161
+ }
1162
+ var fmtElapsed5 = (ms) => {
1163
+ const s2 = Math.floor(ms / 1e3);
1164
+ const m = Math.floor(s2 / 60);
1165
+ const h = Math.floor(m / 60);
1166
+ if (h > 0) return `${h}:${String(m % 60).padStart(2, "0")}:${String(s2 % 60).padStart(2, "0")}`;
1167
+ if (m > 0) return `${m}:${String(s2 % 60).padStart(2, "0")}`;
1168
+ return `${s2}s`;
1169
+ };
1170
+ var WT_STATUS = {
1171
+ allocating: { icon: "\u25CB", color: "gray", label: "allocating" },
1172
+ active: { icon: "\u25CF", color: "yellow", label: "active" },
1173
+ committing: { icon: "\u25D0", color: "cyan", label: "committing" },
1174
+ merging: { icon: "\u21E1", color: "blue", label: "merging" },
1175
+ merged: { icon: "\u2713", color: "green", label: "merged" },
1176
+ "needs-review": { icon: "\u26A0", color: "magenta", label: "needs-review" },
1177
+ failed: { icon: "\u2717", color: "red", label: "failed" }
1178
+ };
1179
+ function fmt(s2) {
1180
+ return WT_STATUS[s2] ?? { icon: "?", color: "white", label: s2 };
1181
+ }
1182
+ function WorktreeMonitor({
1183
+ worktrees,
1184
+ baseBranch,
1185
+ nowTick,
1186
+ onClose
1187
+ }) {
1188
+ useInput((input, key) => {
1189
+ if (key.escape || key.ctrl && input === "w") onClose();
1190
+ });
1191
+ const list = Object.values(worktrees);
1192
+ const active = list.filter((w) => ["active", "committing", "merging"].includes(w.status)).length;
1193
+ const merged = list.filter((w) => w.status === "merged").length;
1194
+ const failed = list.filter((w) => w.status === "failed" || w.status === "needs-review").length;
1195
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "green", paddingX: 1, children: [
1196
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, marginBottom: 1, children: [
1197
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "green", children: "WORKTREE MONITOR" }),
1198
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
1199
+ baseBranch ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1200
+ "base ",
1201
+ baseBranch
1202
+ ] }) : null,
1203
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
1204
+ /* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
1205
+ "\u25B6",
1206
+ active
1207
+ ] }),
1208
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
1209
+ /* @__PURE__ */ jsxs(Text, { color: "green", children: [
1210
+ "\u2713",
1211
+ merged
1212
+ ] }),
1213
+ failed > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
1214
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
1215
+ /* @__PURE__ */ jsxs(Text, { color: "red", children: [
1216
+ "\u2717",
1217
+ failed
1218
+ ] })
1219
+ ] }) : null,
1220
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502 Ctrl+W / Esc to close" })
1221
+ ] }),
1222
+ list.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No worktrees. They appear when AutoPhase runs with isolation on." }) : list.map((w) => {
1223
+ const s2 = fmt(w.status);
1224
+ const short = w.branch.replace(/^wstack\/ap\//, "");
1225
+ const elapsed = w.allocatedAt ? fmtElapsed5(nowTick - w.allocatedAt) : "\u2014";
1226
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
1227
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
1228
+ /* @__PURE__ */ jsx(Text, { color: s2.color, bold: true, children: s2.icon }),
1229
+ /* @__PURE__ */ jsx(Text, { bold: true, children: short }),
1230
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
1231
+ /* @__PURE__ */ jsx(Text, { color: s2.color, children: s2.label }),
1232
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1233
+ "\xB7 elapsed ",
1234
+ elapsed
1235
+ ] })
1236
+ ] }),
1237
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, marginLeft: 2, children: [
1238
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1239
+ w.baseBranch ?? baseBranch ?? "base",
1240
+ " \u2192 ",
1241
+ short
1242
+ ] }),
1243
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1244
+ "\xB7 owner: ",
1245
+ w.ownerLabel
1246
+ ] })
1247
+ ] }),
1248
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, marginLeft: 2, children: [
1249
+ /* @__PURE__ */ jsxs(Text, { color: "green", children: [
1250
+ "+",
1251
+ w.insertions
1252
+ ] }),
1253
+ /* @__PURE__ */ jsxs(Text, { color: "red", children: [
1254
+ "-",
1255
+ w.deletions
1256
+ ] }),
1257
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1258
+ "\xB7 ",
1259
+ w.files,
1260
+ " files"
1261
+ ] })
1262
+ ] }),
1263
+ w.conflictFiles && w.conflictFiles.length > 0 ? /* @__PURE__ */ jsx(Box, { marginLeft: 2, children: /* @__PURE__ */ jsxs(Text, { color: "magenta", children: [
1264
+ "conflicts: ",
1265
+ w.conflictFiles.join(", ")
1266
+ ] }) }) : null
1267
+ ] }, w.branch);
1268
+ }),
1269
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Esc close \xB7 merge conflicts with /worktree merge <branch>" }) })
1270
+ ] });
1271
+ }
1175
1272
 
1176
1273
  // src/markdown-table.ts
1177
1274
  function renderMarkdownTables(text, maxWidth) {
@@ -1409,7 +1506,7 @@ function ToolStreamBox({
1409
1506
  /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
1410
1507
  /* @__PURE__ */ jsx(Text, { color: "yellow", children: "\u25C6 " }),
1411
1508
  /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: name }),
1412
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u23F1 ${fmtDuration2(elapsedMs)}` }),
1509
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u23F1 ${fmtDuration(elapsedMs)}` }),
1413
1510
  hidden > 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` (${totalLines} lines, showing last ${MAX_STREAM_LINES})` }) : null
1414
1511
  ] }),
1415
1512
  /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [
@@ -1504,7 +1601,7 @@ function Entry({
1504
1601
  parts.push(`${entry.outputLines} L`);
1505
1602
  }
1506
1603
  if (entry.outputBytes && entry.outputBytes > 0) {
1507
- parts.push(fmtBytes2(entry.outputBytes));
1604
+ parts.push(fmtBytes(entry.outputBytes));
1508
1605
  }
1509
1606
  if (entry.outputTokens && entry.outputTokens > 0) {
1510
1607
  parts.push(`\u2248${fmtTok2(entry.outputTokens)} tok`);
@@ -1520,7 +1617,7 @@ function Entry({
1520
1617
  /* @__PURE__ */ jsx(Text, { children: " " }),
1521
1618
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: argSummary })
1522
1619
  ] }) : null,
1523
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \xB7 ${fmtDuration2(entry.durationMs)}` }),
1620
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \xB7 ${fmtDuration(entry.durationMs)}` }),
1524
1621
  sizeChip ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \xB7 ${sizeChip}` }) : null
1525
1622
  ] }),
1526
1623
  outLines.map((line, i) => (
@@ -1632,7 +1729,7 @@ function fmtTok2(n) {
1632
1729
  if (n >= 1e3) return `${(n / 1e3).toFixed(n >= 1e4 ? 0 : 1)}k`;
1633
1730
  return String(n);
1634
1731
  }
1635
- function fmtDuration2(ms) {
1732
+ function fmtDuration(ms) {
1636
1733
  if (ms < 1e3) return `${ms}ms`;
1637
1734
  if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
1638
1735
  const totalSec = Math.floor(ms / 1e3);
@@ -1760,7 +1857,7 @@ function formatToolOutput(toolName, output, ok, _outputBytes, outputLines) {
1760
1857
  const bytes = numOf(o["bytes_written"]) ?? numOf(o["bytes"]);
1761
1858
  const created = o["created"] === true;
1762
1859
  const tag = created ? "created" : "updated";
1763
- if (bytes !== void 0) return [`${tag} \xB7 ${fmtBytes2(bytes)}`];
1860
+ if (bytes !== void 0) return [`${tag} \xB7 ${fmtBytes(bytes)}`];
1764
1861
  return [tag];
1765
1862
  }
1766
1863
  }
@@ -1825,17 +1922,17 @@ function formatToolOutput(toolName, output, ok, _outputBytes, outputLines) {
1825
1922
  if (json && typeof json === "object") {
1826
1923
  const o = json;
1827
1924
  const bytes = numOf(o["bytes"]);
1828
- if (bytes !== void 0) return [`${fmtBytes2(bytes)} read`];
1925
+ if (bytes !== void 0) return [`${fmtBytes(bytes)} read`];
1829
1926
  }
1830
1927
  const range = scanNumberedRange(text);
1831
1928
  if (range.count > 0 && range.first !== void 0 && range.last !== void 0) {
1832
1929
  if (range.first === range.last) {
1833
- return [`L${range.first} \xB7 ${fmtBytes2(text.length)}`];
1930
+ return [`L${range.first} \xB7 ${fmtBytes(text.length)}`];
1834
1931
  }
1835
1932
  const contiguous = range.count === range.last - range.first + 1;
1836
1933
  const head = `L${range.first}\u2013${range.last}`;
1837
1934
  const tail = contiguous ? `${range.count} line${range.count === 1 ? "" : "s"}` : `${range.count} lines (gaps)`;
1838
- return [`${head} \xB7 ${tail} \xB7 ${fmtBytes2(text.length)}`];
1935
+ return [`${head} \xB7 ${tail} \xB7 ${fmtBytes(text.length)}`];
1839
1936
  }
1840
1937
  }
1841
1938
  if (toolName === "grep" || toolName === "glob") {
@@ -1893,7 +1990,7 @@ function formatToolOutput(toolName, output, ok, _outputBytes, outputLines) {
1893
1990
  const head = [];
1894
1991
  if (status !== void 0) head.push(`HTTP ${status}`);
1895
1992
  if (ct) head.push(ct.split(";")[0] ?? ct);
1896
- if (content) head.push(fmtBytes2(Buffer.byteLength(content, "utf8")));
1993
+ if (content) head.push(fmtBytes(Buffer.byteLength(content, "utf8")));
1897
1994
  const lines = [];
1898
1995
  if (head.length > 0) lines.push(head.join(" \xB7 "));
1899
1996
  if (url && status !== void 0 && (status < 200 || status >= 400)) {
@@ -1984,7 +2081,7 @@ function formatToolOutput(toolName, output, ok, _outputBytes, outputLines) {
1984
2081
  if (runner && runner !== "none") head.push(runner);
1985
2082
  head.push(`${passed}/${total} passed`);
1986
2083
  if (failed > 0) head.push(`${failed} failed`);
1987
- if (duration !== void 0) head.push(fmtDuration2(duration));
2084
+ if (duration !== void 0) head.push(fmtDuration(duration));
1988
2085
  return [head.join(" \xB7 ")];
1989
2086
  }
1990
2087
  }
@@ -2269,7 +2366,7 @@ function countLines(text) {
2269
2366
  if (!text) return 0;
2270
2367
  return text.replace(/\n$/, "").split("\n").length;
2271
2368
  }
2272
- function fmtBytes2(n) {
2369
+ function fmtBytes(n) {
2273
2370
  if (n < 1024) return `${n}B`;
2274
2371
  if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)}KB`;
2275
2372
  return `${(n / (1024 * 1024)).toFixed(1)}MB`;
@@ -2350,27 +2447,27 @@ function Input({
2350
2447
  hint ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: hint }) : null
2351
2448
  ] });
2352
2449
  }
2353
- function fmtElapsed4(ms) {
2450
+ function fmtElapsed6(ms) {
2354
2451
  if (ms < 1e3) return `${ms}ms`;
2355
2452
  if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
2356
2453
  const m = Math.floor(ms / 6e4);
2357
2454
  const s2 = Math.floor(ms % 6e4 / 1e3);
2358
2455
  return `${m}m${s2.toString().padStart(2, "0")}s`;
2359
2456
  }
2360
- function fmtBytes3(n) {
2457
+ function fmtBytes2(n) {
2361
2458
  if (n < 1024) return `${n}B`;
2362
2459
  return `${(n / 1024).toFixed(1)}KB`;
2363
2460
  }
2364
- function fmtRecentTool2(tool) {
2461
+ function fmtRecentTool(tool) {
2365
2462
  const status = tool.ok === false ? "fail" : "ok";
2366
2463
  const name = tool.name.length > 18 ? `${tool.name.slice(0, 17)}...` : tool.name;
2367
2464
  const parts = [status, name];
2368
- if (typeof tool.durationMs === "number") parts.push(fmtElapsed4(tool.durationMs));
2369
- if (typeof tool.outputBytes === "number" && tool.outputBytes > 0) parts.push(fmtBytes3(tool.outputBytes));
2465
+ if (typeof tool.durationMs === "number") parts.push(fmtElapsed6(tool.durationMs));
2466
+ if (typeof tool.outputBytes === "number" && tool.outputBytes > 0) parts.push(fmtBytes2(tool.outputBytes));
2370
2467
  if (typeof tool.outputLines === "number" && tool.outputLines > 0) parts.push(`${tool.outputLines}L`);
2371
2468
  return parts.join(" ");
2372
2469
  }
2373
- function fmtRecentMessage2(message) {
2470
+ function fmtRecentMessage(message) {
2374
2471
  const text = message.text.replace(/\s+/g, " ");
2375
2472
  return text.length > 48 ? `${text.slice(0, 47)}...` : text;
2376
2473
  }
@@ -2386,9 +2483,9 @@ function LiveActivityStrip({
2386
2483
  running.map((e) => {
2387
2484
  const toolElapsed = e.currentTool ? now - e.currentTool.startedAt : 0;
2388
2485
  const taskElapsed = now - e.startedAt;
2389
- const toolSeg = e.currentTool ? `\u2192 ${e.currentTool.name} (${fmtElapsed4(toolElapsed)})` : "idle between tools";
2390
- const recentTools = (e.recentTools ?? []).slice(-2).map(fmtRecentTool2).join(" | ");
2391
- const messageText = e.streamingText.trim() || (e.recentMessages ?? []).slice(-1).map(fmtRecentMessage2).join("");
2486
+ const toolSeg = e.currentTool ? `\u2192 ${e.currentTool.name} (${fmtElapsed6(toolElapsed)})` : "idle between tools";
2487
+ const recentTools = (e.recentTools ?? []).slice(-2).map(fmtRecentTool).join(" | ");
2488
+ const messageText = e.streamingText.trim() || (e.recentMessages ?? []).slice(-1).map(fmtRecentMessage).join("");
2392
2489
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
2393
2490
  /* @__PURE__ */ jsx(Text, { color: "cyan", children: "\u25CF" }),
2394
2491
  /* @__PURE__ */ jsx(Text, { children: e.name.slice(0, 14).padEnd(14) }),
@@ -2400,7 +2497,7 @@ function LiveActivityStrip({
2400
2497
  "it ",
2401
2498
  e.toolCalls,
2402
2499
  "tc \xB7 ",
2403
- fmtElapsed4(taskElapsed)
2500
+ fmtElapsed6(taskElapsed)
2404
2501
  ] }),
2405
2502
  recentTools ? /* @__PURE__ */ jsxs(Fragment, { children: [
2406
2503
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "|" }),
@@ -2413,7 +2510,7 @@ function LiveActivityStrip({
2413
2510
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "|" }),
2414
2511
  /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2415
2512
  "msg: ",
2416
- fmtRecentMessage2({ text: messageText})
2513
+ fmtRecentMessage({ text: messageText})
2417
2514
  ] })
2418
2515
  ] }) : null
2419
2516
  ] }, e.id);
@@ -3452,6 +3549,34 @@ function reducer(state, action) {
3452
3549
  case "autoPhaseReset": {
3453
3550
  return { ...state, autoPhase: null };
3454
3551
  }
3552
+ case "worktreeUpsert": {
3553
+ const prev = state.worktrees[action.handleId];
3554
+ const merged = {
3555
+ branch: "",
3556
+ ownerLabel: "",
3557
+ status: "active",
3558
+ insertions: 0,
3559
+ deletions: 0,
3560
+ files: 0,
3561
+ allocatedAt: Date.now(),
3562
+ ...prev,
3563
+ ...action.row
3564
+ };
3565
+ return {
3566
+ ...state,
3567
+ worktrees: { ...state.worktrees, [action.handleId]: merged },
3568
+ worktreeBase: action.baseBranch ?? state.worktreeBase
3569
+ };
3570
+ }
3571
+ case "worktreeRemove": {
3572
+ if (!state.worktrees[action.handleId]) return state;
3573
+ const next = { ...state.worktrees };
3574
+ delete next[action.handleId];
3575
+ return { ...state, worktrees: next };
3576
+ }
3577
+ case "worktreeMonitorToggle": {
3578
+ return { ...state, worktreeMonitorOpen: !state.worktreeMonitorOpen };
3579
+ }
3455
3580
  }
3456
3581
  }
3457
3582
  var PASTE_THRESHOLD_CHARS = 200;
@@ -3625,7 +3750,9 @@ function App({
3625
3750
  rewindOverlay: null,
3626
3751
  eternalStage: null,
3627
3752
  goalSummary: null,
3628
- autoPhase: null
3753
+ autoPhase: null,
3754
+ worktrees: {},
3755
+ worktreeMonitorOpen: false
3629
3756
  });
3630
3757
  const builderRef = useRef(null);
3631
3758
  if (builderRef.current === null) {
@@ -3651,10 +3778,10 @@ function App({
3651
3778
  const handleRewindTo = React2.useCallback(async (checkpointIndex) => {
3652
3779
  const sessionId = agent.ctx.session.id;
3653
3780
  if (!sessionId) return;
3654
- const rewinder = new DefaultSessionRewinder(sessionsDir ?? "");
3781
+ const rewinder = new DefaultSessionRewinder(sessionsDir ?? "", projectRoot ?? agent.ctx.cwd);
3655
3782
  await rewinder.rewindToCheckpoint(sessionId, checkpointIndex);
3656
3783
  await agent.ctx.session.truncateToCheckpoint(checkpointIndex);
3657
- }, [agent.ctx.session, sessionsDir]);
3784
+ }, [agent.ctx.session, sessionsDir, projectRoot, agent.ctx.cwd]);
3658
3785
  const setDraft = (buffer, cursor) => {
3659
3786
  draftRef.current = { buffer, cursor };
3660
3787
  dispatch({ type: "setBuffer", buffer, cursor });
@@ -4490,6 +4617,45 @@ function App({
4490
4617
  dispatch({ type: "autoPhaseReset" });
4491
4618
  break;
4492
4619
  }
4620
+ case "worktree.allocated": {
4621
+ const p = payload;
4622
+ dispatch({
4623
+ type: "worktreeUpsert",
4624
+ handleId: p.handleId,
4625
+ baseBranch: p.baseBranch,
4626
+ row: { branch: p.branch, ownerLabel: p.ownerLabel, baseBranch: p.baseBranch, status: "active", allocatedAt: Date.now() }
4627
+ });
4628
+ break;
4629
+ }
4630
+ case "worktree.committed": {
4631
+ const p = payload;
4632
+ dispatch({
4633
+ type: "worktreeUpsert",
4634
+ handleId: p.handleId,
4635
+ row: { insertions: p.insertions, deletions: p.deletions, files: p.files, status: "committing" }
4636
+ });
4637
+ break;
4638
+ }
4639
+ case "worktree.merged": {
4640
+ const p = payload;
4641
+ dispatch({ type: "worktreeUpsert", handleId: p.handleId, row: { status: "merged" } });
4642
+ break;
4643
+ }
4644
+ case "worktree.conflict": {
4645
+ const p = payload;
4646
+ dispatch({ type: "worktreeUpsert", handleId: p.handleId, row: { status: "needs-review", conflictFiles: p.conflictFiles } });
4647
+ break;
4648
+ }
4649
+ case "worktree.failed": {
4650
+ const p = payload;
4651
+ dispatch({ type: "worktreeUpsert", handleId: p.handleId, row: { status: "failed" } });
4652
+ break;
4653
+ }
4654
+ case "worktree.released": {
4655
+ const p = payload;
4656
+ if (!p.kept) dispatch({ type: "worktreeRemove", handleId: p.handleId });
4657
+ break;
4658
+ }
4493
4659
  }
4494
4660
  };
4495
4661
  return subscribeAutoPhase(handler);
@@ -5023,6 +5189,10 @@ function App({
5023
5189
  dispatch({ type: "autoPhaseMonitorToggle" });
5024
5190
  return;
5025
5191
  }
5192
+ if (key.ctrl && input === "t") {
5193
+ dispatch({ type: "worktreeMonitorToggle" });
5194
+ return;
5195
+ }
5026
5196
  if (state.autonomyPicker.open) {
5027
5197
  if (key.escape) {
5028
5198
  dispatch({ type: "autonomyPickerClose" });
@@ -5266,6 +5436,10 @@ function App({
5266
5436
  return;
5267
5437
  }
5268
5438
  if (key.ctrl && input === "w") {
5439
+ if (state.worktreeMonitorOpen) {
5440
+ dispatch({ type: "worktreeMonitorToggle" });
5441
+ return;
5442
+ }
5269
5443
  if (cursor === 0) return;
5270
5444
  const beforeCursor = buffer.slice(0, cursor);
5271
5445
  const lastWordStart = beforeCursor.lastIndexOf(" ") + 1;
@@ -5752,6 +5926,14 @@ User message:
5752
5926
  nowTick,
5753
5927
  onClose: () => dispatch({ type: "autoPhaseMonitorToggle" })
5754
5928
  }
5929
+ ) : state.worktreeMonitorOpen ? /* @__PURE__ */ jsx(
5930
+ WorktreeMonitor,
5931
+ {
5932
+ worktrees: state.worktrees,
5933
+ baseBranch: state.worktreeBase,
5934
+ nowTick,
5935
+ onClose: () => dispatch({ type: "worktreeMonitorToggle" })
5936
+ }
5755
5937
  ) : state.monitorOpen ? /* @__PURE__ */ jsx(
5756
5938
  FleetMonitor,
5757
5939
  {
@@ -5768,7 +5950,8 @@ User message:
5768
5950
  runningPhaseIds: state.autoPhase.runningPhaseIds,
5769
5951
  nowTick
5770
5952
  }
5771
- ) : null
5953
+ ) : null,
5954
+ Object.keys(state.worktrees).length > 0 && !state.worktreeMonitorOpen && !state.monitorOpen ? /* @__PURE__ */ jsx(WorktreePanel, { worktrees: state.worktrees, nowTick }) : null
5772
5955
  ] });
5773
5956
  }
5774
5957
  function renderRunningTools(running) {
@@ -5803,6 +5986,75 @@ function fmtTok3(n) {
5803
5986
  return `${(n / 1e6).toFixed(1)}M`;
5804
5987
  }
5805
5988
 
5989
+ // src/terminal-title.ts
5990
+ var SPINNER = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
5991
+ var setTitle = (s2) => `\x1B]0;${s2}\x07`;
5992
+ function shortModel(model) {
5993
+ if (!model) return "";
5994
+ const base = model.split("/").pop() ?? model;
5995
+ return base.replace(/-\d{8}$/, "").replace(/\[.*?\]$/, "");
5996
+ }
5997
+ function marquee(text, offset, width) {
5998
+ const padded = `${text} `;
5999
+ const start = offset % padded.length;
6000
+ return (padded + padded).slice(start, start + width);
6001
+ }
6002
+ function startTerminalTitle(opts) {
6003
+ const { stdout, events } = opts;
6004
+ if (process.env["WRONGSTACK_NO_TITLE"] === "1" || !stdout.isTTY) {
6005
+ return () => {
6006
+ };
6007
+ }
6008
+ const app = opts.appName ?? "WrongStack";
6009
+ const model = shortModel(opts.model);
6010
+ const idleAfter = opts.idleAfterMs ?? 3500;
6011
+ const suffix = model ? ` \xB7 ${model}` : "";
6012
+ let frame = 0;
6013
+ let scroll = 0;
6014
+ let phase = "idle";
6015
+ let toolName = "";
6016
+ let lastActivity = 0;
6017
+ const touch = (next, tool) => {
6018
+ phase = next;
6019
+ if (tool) toolName = tool;
6020
+ lastActivity = Date.now();
6021
+ };
6022
+ const offs = [
6023
+ events.on("iteration.started", () => touch("thinking")),
6024
+ events.on("provider.text_delta", () => touch("thinking")),
6025
+ events.on("provider.thinking_delta", () => touch("thinking")),
6026
+ events.on("tool.started", (e) => touch("tool", e.name ?? "tool")),
6027
+ events.on("tool.executed", () => touch("thinking"))
6028
+ ];
6029
+ const write = (s2) => {
6030
+ try {
6031
+ stdout.write(s2);
6032
+ } catch {
6033
+ }
6034
+ };
6035
+ const timer = setInterval(() => {
6036
+ frame = (frame + 1) % SPINNER.length;
6037
+ scroll += 1;
6038
+ if (lastActivity && Date.now() - lastActivity > idleAfter) phase = "idle";
6039
+ const sp = SPINNER[frame];
6040
+ let title;
6041
+ if (phase === "tool") {
6042
+ title = `${sp} \u25B8 ${toolName}${suffix}`;
6043
+ } else if (phase === "thinking") {
6044
+ title = `${sp} thinking\u2026${suffix}`;
6045
+ } else {
6046
+ title = marquee(`\u2726 ${app}${suffix}`, scroll >> 1, 22);
6047
+ }
6048
+ write(setTitle(title));
6049
+ }, opts.intervalMs ?? 130);
6050
+ timer.unref?.();
6051
+ return () => {
6052
+ clearInterval(timer);
6053
+ for (const off of offs) off();
6054
+ write(setTitle(model ? `${app} \xB7 ${model}` : app));
6055
+ };
6056
+ }
6057
+
5806
6058
  // src/run-tui.ts
5807
6059
  var BRACKETED_PASTE_ON = "\x1B[?2004h";
5808
6060
  var BRACKETED_PASTE_OFF = "\x1B[?2004l";
@@ -5824,6 +6076,7 @@ async function runTui(opts) {
5824
6076
  stdout.write(CURSOR_HOME);
5825
6077
  }
5826
6078
  stdout.write(BRACKETED_PASTE_ON);
6079
+ const stopTitle = startTerminalTitle({ stdout, events: opts.events, model: opts.model });
5827
6080
  const swallowSignals = ["SIGTSTP", "SIGQUIT", "SIGTTIN", "SIGTTOU"];
5828
6081
  const swallow = () => {
5829
6082
  };
@@ -5837,6 +6090,10 @@ async function runTui(opts) {
5837
6090
  const cleanup = () => {
5838
6091
  if (cleaned) return;
5839
6092
  cleaned = true;
6093
+ try {
6094
+ stopTitle();
6095
+ } catch {
6096
+ }
5840
6097
  try {
5841
6098
  stdout.write(BRACKETED_PASTE_OFF);
5842
6099
  if (useAltScreen) {