@wrongstack/tui 0.8.4 → 0.8.6

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
@@ -16,8 +16,8 @@ function stringifyInput(input) {
16
16
  const obj = input;
17
17
  return Object.entries(obj).filter(([k]) => k !== "content" && k !== "new_string").map(([k, v]) => `${k}: ${truncate(JSON.stringify(v), 80)}`).join(" ");
18
18
  }
19
- function truncate(s, max) {
20
- return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
19
+ function truncate(s2, max) {
20
+ return s2.length <= max ? s2 : `${s2.slice(0, max - 1)}\u2026`;
21
21
  }
22
22
  function hasDiff(input) {
23
23
  return Boolean(
@@ -677,11 +677,11 @@ function fmtElapsed(ms) {
677
677
  const totalSec = Math.floor(ms / 1e3);
678
678
  const h = Math.floor(totalSec / 3600);
679
679
  const m = Math.floor(totalSec % 3600 / 60);
680
- const s = totalSec % 60;
680
+ const s2 = totalSec % 60;
681
681
  if (h > 0) {
682
- return `${h}:${pad2(m)}:${pad2(s)}`;
682
+ return `${h}:${pad2(m)}:${pad2(s2)}`;
683
683
  }
684
- return `${pad2(m)}:${pad2(s)}`;
684
+ return `${pad2(m)}:${pad2(s2)}`;
685
685
  }
686
686
  function pad2(n) {
687
687
  return n < 10 ? `0${n}` : String(n);
@@ -751,11 +751,11 @@ function FleetMonitor({
751
751
  for (const e of all) {
752
752
  events.push({ at: e.startedAt, icon: "\u25CF", color: "cyan", text: `${e.name} spawned` });
753
753
  if (e.status !== "running" && e.status !== "idle") {
754
- const s = STATUS[e.status];
754
+ const s2 = STATUS[e.status];
755
755
  events.push({
756
756
  at: e.lastEventAt,
757
- icon: s.icon,
758
- color: s.color,
757
+ icon: s2.icon,
758
+ color: s2.color,
759
759
  text: `${e.name} ${e.status} (${e.toolCalls}t)`
760
760
  });
761
761
  }
@@ -824,12 +824,12 @@ function FleetMonitor({
824
824
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "cost" })
825
825
  ] }),
826
826
  shown.map((e) => {
827
- const s = STATUS[e.status];
827
+ const s2 = STATUS[e.status];
828
828
  const elapsed = e.status === "running" ? fmtElapsed(Math.max(0, nowTick - e.startedAt)) : fmtElapsed(Math.max(0, nowTick - e.lastEventAt)) + " ago";
829
829
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
830
- /* @__PURE__ */ jsx(Text, { color: s.color, children: s.icon }),
830
+ /* @__PURE__ */ jsx(Text, { color: s2.color, children: s2.icon }),
831
831
  /* @__PURE__ */ jsx(Text, { children: e.name.padEnd(16).slice(0, 16) }),
832
- /* @__PURE__ */ jsx(Text, { color: s.color, children: e.status.padEnd(10) }),
832
+ /* @__PURE__ */ jsx(Text, { color: s2.color, children: e.status.padEnd(10) }),
833
833
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: `L${e.iterations} ${e.toolCalls}t`.padEnd(8) }),
834
834
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: elapsed.padEnd(8).slice(0, 8) }),
835
835
  /* @__PURE__ */ jsx(Text, { color: "yellow", children: fmtCost2(e.cost) }),
@@ -870,8 +870,8 @@ function fmtTokens2(n) {
870
870
  if (n < 1e6) return `${(n / 1e3).toFixed(1)}k`;
871
871
  return `${(n / 1e6).toFixed(1)}M`;
872
872
  }
873
- function snippet(s, max = 72) {
874
- const oneLine2 = s.replace(/\s+/g, " ").trim();
873
+ function snippet(s2, max = 72) {
874
+ const oneLine2 = s2.replace(/\s+/g, " ").trim();
875
875
  if (oneLine2.length <= max) return oneLine2;
876
876
  return `${oneLine2.slice(0, max - 1)}\u2026`;
877
877
  }
@@ -931,7 +931,7 @@ function AgentsMonitor({
931
931
  ] }),
932
932
  shown.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No live agents \u2014 spawn with /spawn or /fleet dispatch." }) : null,
933
933
  shown.map((e) => {
934
- const s = STATUS2[e.status];
934
+ const s2 = STATUS2[e.status];
935
935
  const elapsed = e.status === "running" ? fmtElapsed(Math.max(0, nowTick - e.startedAt)) : e.status;
936
936
  const spark = sparkline(bucketActivity(e.recentTools, nowTick));
937
937
  const lastTool = e.recentTools[e.recentTools.length - 1];
@@ -940,7 +940,7 @@ function AgentsMonitor({
940
940
  const toolElapsed = e.currentTool ? Math.max(0, nowTick - e.currentTool.startedAt) : 0;
941
941
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
942
942
  /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
943
- /* @__PURE__ */ jsx(Text, { color: s.color, bold: true, children: s.icon }),
943
+ /* @__PURE__ */ jsx(Text, { color: s2.color, bold: true, children: s2.icon }),
944
944
  /* @__PURE__ */ jsx(Text, { bold: true, children: e.name }),
945
945
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
946
946
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: elapsed }),
@@ -999,6 +999,377 @@ function AgentsMonitor({
999
999
  })
1000
1000
  ] });
1001
1001
  }
1002
+ var fmtElapsed2 = (ms) => {
1003
+ const s2 = Math.floor(ms / 1e3);
1004
+ const m = Math.floor(s2 / 60);
1005
+ const h = Math.floor(m / 60);
1006
+ if (h > 0) return `${h}:${String(m % 60).padStart(2, "0")}:${String(s2 % 60).padStart(2, "0")}`;
1007
+ if (m > 0) return `${m}:${String(s2 % 60).padStart(2, "0")}`;
1008
+ return `${s2}s`;
1009
+ };
1010
+ var PHASE_STATUS = {
1011
+ pending: { icon: "\u23F3", color: "gray", label: "pending" },
1012
+ ready: { icon: "\u{1F51C}", color: "cyan", label: "ready" },
1013
+ running: { icon: "\u25B6", color: "yellow", label: "running" },
1014
+ paused: { icon: "\u23F8", color: "magenta", label: "paused" },
1015
+ completed: { icon: "\u2713", color: "green", label: "done" },
1016
+ failed: { icon: "\u2717", color: "red", label: "failed" },
1017
+ skipped: { icon: "\u23ED", color: "gray", label: "skipped" }
1018
+ };
1019
+ function fmtPhase(s2) {
1020
+ return PHASE_STATUS[s2] ?? { icon: "?", color: "white", label: s2 };
1021
+ }
1022
+ function PhaseMonitor({
1023
+ phases,
1024
+ runningPhaseIds,
1025
+ elapsedMs,
1026
+ nowTick,
1027
+ onClose
1028
+ }) {
1029
+ useInput((_, key) => {
1030
+ if (key.escape) onClose();
1031
+ });
1032
+ const phaseList = Object.values(phases);
1033
+ const running = phaseList.filter((p) => runningPhaseIds.includes(Object.keys(phases).find((k) => phases[k] === p) ?? ""));
1034
+ const done = phaseList.filter((p) => p.status === "completed" || p.status === "skipped");
1035
+ const failed = phaseList.filter((p) => p.status === "failed");
1036
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [
1037
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, marginBottom: 1, children: [
1038
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "PHASE MONITOR" }),
1039
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
1040
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1041
+ "\u23F1 ",
1042
+ fmtElapsed2(elapsedMs)
1043
+ ] }),
1044
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
1045
+ /* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
1046
+ "\u25B6",
1047
+ running.length
1048
+ ] }),
1049
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
1050
+ /* @__PURE__ */ jsxs(Text, { color: "green", children: [
1051
+ "\u2713",
1052
+ done.length
1053
+ ] }),
1054
+ failed.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
1055
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
1056
+ /* @__PURE__ */ jsxs(Text, { color: "red", children: [
1057
+ "\u2717",
1058
+ failed.length
1059
+ ] })
1060
+ ] }) : null,
1061
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502 Ctrl+P / Esc to close" })
1062
+ ] }),
1063
+ phaseList.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No phases active. Use /autophase start [title] to begin." }) : phaseList.map((phase, i) => {
1064
+ const s2 = fmtPhase(phase.status);
1065
+ const phaseKey = Object.keys(phases).find((k) => phases[k] === phase) ?? String(i);
1066
+ const isRunning = runningPhaseIds.includes(phaseKey);
1067
+ const elapsed = phase.startedAt ? fmtElapsed2(nowTick - phase.startedAt) : "\u2014";
1068
+ const progress = phase.totalTasks > 0 ? `${phase.completedTasks}/${phase.totalTasks}` : "\u2014";
1069
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginTop: 1, children: /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
1070
+ /* @__PURE__ */ jsx(Text, { color: s2.color, bold: true, children: s2.icon }),
1071
+ /* @__PURE__ */ jsx(Text, { bold: true, children: phase.name }),
1072
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
1073
+ /* @__PURE__ */ jsx(Text, { color: s2.color, children: s2.label }),
1074
+ isRunning ? /* @__PURE__ */ jsxs(Fragment, { children: [
1075
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
1076
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1077
+ "elapsed ",
1078
+ elapsed
1079
+ ] })
1080
+ ] }) : null,
1081
+ phase.totalTasks > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
1082
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
1083
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1084
+ "tasks ",
1085
+ progress
1086
+ ] })
1087
+ ] })
1088
+ ] }) }, phaseKey);
1089
+ }),
1090
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191/\u2193 navigate phases \xB7 Esc close" }) })
1091
+ ] });
1092
+ }
1093
+ var fmtElapsed3 = (ms) => {
1094
+ const s2 = Math.floor(ms / 1e3);
1095
+ const m = Math.floor(s2 / 60);
1096
+ const h = Math.floor(m / 60);
1097
+ if (h > 0) return `${h}:${String(m % 60).padStart(2, "0")}:${String(s2 % 60).padStart(2, "0")}`;
1098
+ if (m > 0) return `${m}:${String(s2 % 60).padStart(2, "0")}`;
1099
+ return `${s2}s`;
1100
+ };
1101
+ var STATUS3 = {
1102
+ pending: { icon: "\u25CB", color: "gray" },
1103
+ ready: { icon: "\u25D0", color: "cyan" },
1104
+ running: { icon: "\u25CF", color: "yellow" },
1105
+ paused: { icon: "\u23F8", color: "magenta" },
1106
+ completed: { icon: "\u2713", color: "green" },
1107
+ failed: { icon: "\u2717", color: "red" },
1108
+ skipped: { icon: "\u2298", color: "gray" }
1109
+ };
1110
+ function s(entry) {
1111
+ return STATUS3[entry] ?? { icon: "?", color: "white" };
1112
+ }
1113
+ function PhasePanel({ phases, runningPhaseIds, nowTick }) {
1114
+ const list = Object.values(phases);
1115
+ if (list.length === 0) return null;
1116
+ const done = list.filter((p) => p.status === "completed" || p.status === "skipped").length;
1117
+ const running = list.filter((p) => p.status === "running").length;
1118
+ const failed = list.filter((p) => p.status === "failed").length;
1119
+ return /* @__PURE__ */ jsxs(
1120
+ Box,
1121
+ {
1122
+ flexDirection: "column",
1123
+ paddingX: 1,
1124
+ borderStyle: "single",
1125
+ borderTop: false,
1126
+ borderBottom: false,
1127
+ borderLeft: false,
1128
+ borderRight: false,
1129
+ children: [
1130
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 2, children: [
1131
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Phases" }),
1132
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
1133
+ /* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
1134
+ "\u25B6",
1135
+ running
1136
+ ] }),
1137
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1138
+ "\u2713",
1139
+ done
1140
+ ] }),
1141
+ failed > 0 ? /* @__PURE__ */ jsxs(Text, { color: "red", children: [
1142
+ "\u2717",
1143
+ failed
1144
+ ] }) : null,
1145
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1146
+ "\xB7 ",
1147
+ list.length,
1148
+ " total"
1149
+ ] }),
1150
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7 Ctrl+P for details" })
1151
+ ] }),
1152
+ list.map((phase, i) => {
1153
+ const phaseKey = Object.keys(phases).find((k) => phases[k] === phase) ?? String(i);
1154
+ const st2 = s(phase.status);
1155
+ const progress = phase.totalTasks > 0 ? `${phase.completedTasks}/${phase.totalTasks}` : "";
1156
+ const elapsed = phase.startedAt ? fmtElapsed3(nowTick - phase.startedAt) : "";
1157
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
1158
+ /* @__PURE__ */ jsx(Text, { color: st2.color, children: st2.icon }),
1159
+ /* @__PURE__ */ jsx(Text, { children: phase.name.slice(0, 14).padEnd(14) }),
1160
+ /* @__PURE__ */ jsx(Text, { color: st2.color, dimColor: true, children: st2.icon === "\u25CF" ? phase.status : "" }),
1161
+ progress ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1162
+ " ",
1163
+ progress
1164
+ ] }) : null,
1165
+ elapsed && st2.icon === "\u25CF" ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1166
+ " ",
1167
+ elapsed
1168
+ ] }) : null
1169
+ ] }, phaseKey);
1170
+ })
1171
+ ]
1172
+ }
1173
+ );
1174
+ }
1175
+ var fmtElapsed4 = (ms) => {
1176
+ const s2 = Math.floor(ms / 1e3);
1177
+ const m = Math.floor(s2 / 60);
1178
+ const h = Math.floor(m / 60);
1179
+ if (h > 0) return `${h}:${String(m % 60).padStart(2, "0")}:${String(s2 % 60).padStart(2, "0")}`;
1180
+ if (m > 0) return `${m}:${String(s2 % 60).padStart(2, "0")}`;
1181
+ return `${s2}s`;
1182
+ };
1183
+ var STATUS4 = {
1184
+ allocating: { icon: "\u25CB", color: "gray" },
1185
+ active: { icon: "\u25CF", color: "yellow" },
1186
+ committing: { icon: "\u25D0", color: "cyan" },
1187
+ merging: { icon: "\u21E1", color: "blue" },
1188
+ merged: { icon: "\u2713", color: "green" },
1189
+ "needs-review": { icon: "\u26A0", color: "magenta" },
1190
+ failed: { icon: "\u2717", color: "red" }
1191
+ };
1192
+ function st(status) {
1193
+ return STATUS4[status] ?? { icon: "?", color: "white" };
1194
+ }
1195
+ function WorktreePanel({
1196
+ worktrees,
1197
+ nowTick
1198
+ }) {
1199
+ const list = Object.values(worktrees);
1200
+ if (list.length === 0) return null;
1201
+ const active = list.filter((w) => w.status === "active" || w.status === "committing" || w.status === "merging").length;
1202
+ const merged = list.filter((w) => w.status === "merged").length;
1203
+ const failed = list.filter((w) => w.status === "failed" || w.status === "needs-review").length;
1204
+ return /* @__PURE__ */ jsxs(
1205
+ Box,
1206
+ {
1207
+ flexDirection: "column",
1208
+ paddingX: 1,
1209
+ borderStyle: "single",
1210
+ borderTop: false,
1211
+ borderBottom: false,
1212
+ borderLeft: false,
1213
+ borderRight: false,
1214
+ children: [
1215
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 2, children: [
1216
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Worktrees" }),
1217
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
1218
+ /* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
1219
+ "\u25B6",
1220
+ active
1221
+ ] }),
1222
+ /* @__PURE__ */ jsxs(Text, { color: "green", children: [
1223
+ "\u2713",
1224
+ merged
1225
+ ] }),
1226
+ failed > 0 ? /* @__PURE__ */ jsxs(Text, { color: "red", children: [
1227
+ "\u2717",
1228
+ failed
1229
+ ] }) : null,
1230
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1231
+ "\xB7 ",
1232
+ list.length,
1233
+ " total \xB7 Ctrl+W for details"
1234
+ ] })
1235
+ ] }),
1236
+ list.map((w) => {
1237
+ const s2 = st(w.status);
1238
+ const conflict = w.status === "needs-review";
1239
+ const elapsed = w.allocatedAt ? fmtElapsed4(nowTick - w.allocatedAt) : "";
1240
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
1241
+ /* @__PURE__ */ jsx(Text, { color: s2.color, children: s2.icon }),
1242
+ /* @__PURE__ */ jsx(Text, { children: w.branch.replace(/^wstack\/ap\//, "").slice(0, 18).padEnd(18) }),
1243
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: w.ownerLabel.slice(0, 12) }),
1244
+ conflict ? /* @__PURE__ */ jsx(Text, { color: "magenta", children: " CONFLICT" }) : /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1245
+ " +",
1246
+ w.insertions,
1247
+ "/-",
1248
+ w.deletions,
1249
+ " ",
1250
+ w.files,
1251
+ "f"
1252
+ ] }),
1253
+ elapsed && (w.status === "active" || w.status === "committing") ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1254
+ " ",
1255
+ elapsed
1256
+ ] }) : null
1257
+ ] }, w.branch);
1258
+ })
1259
+ ]
1260
+ }
1261
+ );
1262
+ }
1263
+ var fmtElapsed5 = (ms) => {
1264
+ const s2 = Math.floor(ms / 1e3);
1265
+ const m = Math.floor(s2 / 60);
1266
+ const h = Math.floor(m / 60);
1267
+ if (h > 0) return `${h}:${String(m % 60).padStart(2, "0")}:${String(s2 % 60).padStart(2, "0")}`;
1268
+ if (m > 0) return `${m}:${String(s2 % 60).padStart(2, "0")}`;
1269
+ return `${s2}s`;
1270
+ };
1271
+ var WT_STATUS = {
1272
+ allocating: { icon: "\u25CB", color: "gray", label: "allocating" },
1273
+ active: { icon: "\u25CF", color: "yellow", label: "active" },
1274
+ committing: { icon: "\u25D0", color: "cyan", label: "committing" },
1275
+ merging: { icon: "\u21E1", color: "blue", label: "merging" },
1276
+ merged: { icon: "\u2713", color: "green", label: "merged" },
1277
+ "needs-review": { icon: "\u26A0", color: "magenta", label: "needs-review" },
1278
+ failed: { icon: "\u2717", color: "red", label: "failed" }
1279
+ };
1280
+ function fmt(s2) {
1281
+ return WT_STATUS[s2] ?? { icon: "?", color: "white", label: s2 };
1282
+ }
1283
+ function WorktreeMonitor({
1284
+ worktrees,
1285
+ baseBranch,
1286
+ nowTick,
1287
+ onClose
1288
+ }) {
1289
+ useInput((_, key) => {
1290
+ if (key.escape) onClose();
1291
+ });
1292
+ const list = Object.values(worktrees);
1293
+ const active = list.filter((w) => ["active", "committing", "merging"].includes(w.status)).length;
1294
+ const merged = list.filter((w) => w.status === "merged").length;
1295
+ const failed = list.filter((w) => w.status === "failed" || w.status === "needs-review").length;
1296
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "green", paddingX: 1, children: [
1297
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, marginBottom: 1, children: [
1298
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "green", children: "WORKTREE MONITOR" }),
1299
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
1300
+ baseBranch ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1301
+ "base ",
1302
+ baseBranch
1303
+ ] }) : null,
1304
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
1305
+ /* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
1306
+ "\u25B6",
1307
+ active
1308
+ ] }),
1309
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
1310
+ /* @__PURE__ */ jsxs(Text, { color: "green", children: [
1311
+ "\u2713",
1312
+ merged
1313
+ ] }),
1314
+ failed > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
1315
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
1316
+ /* @__PURE__ */ jsxs(Text, { color: "red", children: [
1317
+ "\u2717",
1318
+ failed
1319
+ ] })
1320
+ ] }) : null,
1321
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502 Ctrl+W / Esc to close" })
1322
+ ] }),
1323
+ list.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No worktrees. They appear when AutoPhase runs with isolation on." }) : list.map((w) => {
1324
+ const s2 = fmt(w.status);
1325
+ const short = w.branch.replace(/^wstack\/ap\//, "");
1326
+ const elapsed = w.allocatedAt ? fmtElapsed5(nowTick - w.allocatedAt) : "\u2014";
1327
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
1328
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
1329
+ /* @__PURE__ */ jsx(Text, { color: s2.color, bold: true, children: s2.icon }),
1330
+ /* @__PURE__ */ jsx(Text, { bold: true, children: short }),
1331
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\xB7" }),
1332
+ /* @__PURE__ */ jsx(Text, { color: s2.color, children: s2.label }),
1333
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1334
+ "\xB7 elapsed ",
1335
+ elapsed
1336
+ ] })
1337
+ ] }),
1338
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, marginLeft: 2, children: [
1339
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1340
+ w.baseBranch ?? baseBranch ?? "base",
1341
+ " \u2192 ",
1342
+ short
1343
+ ] }),
1344
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1345
+ "\xB7 owner: ",
1346
+ w.ownerLabel
1347
+ ] })
1348
+ ] }),
1349
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, marginLeft: 2, children: [
1350
+ /* @__PURE__ */ jsxs(Text, { color: "green", children: [
1351
+ "+",
1352
+ w.insertions
1353
+ ] }),
1354
+ /* @__PURE__ */ jsxs(Text, { color: "red", children: [
1355
+ "-",
1356
+ w.deletions
1357
+ ] }),
1358
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1359
+ "\xB7 ",
1360
+ w.files,
1361
+ " files"
1362
+ ] })
1363
+ ] }),
1364
+ w.conflictFiles && w.conflictFiles.length > 0 ? /* @__PURE__ */ jsx(Box, { marginLeft: 2, children: /* @__PURE__ */ jsxs(Text, { color: "magenta", children: [
1365
+ "conflicts: ",
1366
+ w.conflictFiles.join(", ")
1367
+ ] }) }) : null
1368
+ ] }, w.branch);
1369
+ }),
1370
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Esc close \xB7 merge conflicts with /worktree merge <branch>" }) })
1371
+ ] });
1372
+ }
1002
1373
 
1003
1374
  // src/markdown-table.ts
1004
1375
  function renderMarkdownTables(text, maxWidth) {
@@ -1095,7 +1466,7 @@ function computeWidths(allRows, cols, maxWidth) {
1095
1466
  if (w > natural[c]) natural[c] = Math.min(total + 1, w);
1096
1467
  }
1097
1468
  }
1098
- const sumNatural = natural.reduce((s, n) => s + n, 0);
1469
+ const sumNatural = natural.reduce((s2, n) => s2 + n, 0);
1099
1470
  if (sumNatural <= avail) return natural;
1100
1471
  const widths = natural.slice();
1101
1472
  let sum = sumNatural;
@@ -1116,9 +1487,9 @@ function computeWidths(allRows, cols, maxWidth) {
1116
1487
  return widths;
1117
1488
  }
1118
1489
  var MIN_COL_WIDTH = 4;
1119
- function longestWord(s) {
1490
+ function longestWord(s2) {
1120
1491
  let max = 0;
1121
- for (const w of s.split(/\s+/)) if (w.length > max) max = w.length;
1492
+ for (const w of s2.split(/\s+/)) if (w.length > max) max = w.length;
1122
1493
  return max;
1123
1494
  }
1124
1495
  function border(left, mid, right, widths) {
@@ -2066,11 +2437,11 @@ function stringOf(v) {
2066
2437
  function numOf(v) {
2067
2438
  return typeof v === "number" && Number.isFinite(v) ? v : void 0;
2068
2439
  }
2069
- function tryParseJson(s) {
2070
- const t = s.trimStart();
2440
+ function tryParseJson(s2) {
2441
+ const t = s2.trimStart();
2071
2442
  if (!t.startsWith("{") && !t.startsWith("[")) return void 0;
2072
2443
  try {
2073
- return JSON.parse(s);
2444
+ return JSON.parse(s2);
2074
2445
  } catch {
2075
2446
  return void 0;
2076
2447
  }
@@ -2101,9 +2472,9 @@ function fmtBytes2(n) {
2101
2472
  if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)}KB`;
2102
2473
  return `${(n / (1024 * 1024)).toFixed(1)}MB`;
2103
2474
  }
2104
- function truncMid(s, max) {
2105
- if (s.length <= max) return s;
2106
- return `${s.slice(0, max - 1)}\u2026`;
2475
+ function truncMid(s2, max) {
2476
+ if (s2.length <= max) return s2;
2477
+ return `${s2.slice(0, max - 1)}\u2026`;
2107
2478
  }
2108
2479
  function isHomeEnd(data) {
2109
2480
  if (data === "\x1B[H" || data === "\x1B[1~" || data === "\x1BOH" || data === "\x1B[7~")
@@ -2177,12 +2548,12 @@ function Input({
2177
2548
  hint ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: hint }) : null
2178
2549
  ] });
2179
2550
  }
2180
- function fmtElapsed2(ms) {
2551
+ function fmtElapsed6(ms) {
2181
2552
  if (ms < 1e3) return `${ms}ms`;
2182
2553
  if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
2183
2554
  const m = Math.floor(ms / 6e4);
2184
- const s = Math.floor(ms % 6e4 / 1e3);
2185
- return `${m}m${s.toString().padStart(2, "0")}s`;
2555
+ const s2 = Math.floor(ms % 6e4 / 1e3);
2556
+ return `${m}m${s2.toString().padStart(2, "0")}s`;
2186
2557
  }
2187
2558
  function fmtBytes3(n) {
2188
2559
  if (n < 1024) return `${n}B`;
@@ -2192,7 +2563,7 @@ function fmtRecentTool2(tool) {
2192
2563
  const status = tool.ok === false ? "fail" : "ok";
2193
2564
  const name = tool.name.length > 18 ? `${tool.name.slice(0, 17)}...` : tool.name;
2194
2565
  const parts = [status, name];
2195
- if (typeof tool.durationMs === "number") parts.push(fmtElapsed2(tool.durationMs));
2566
+ if (typeof tool.durationMs === "number") parts.push(fmtElapsed6(tool.durationMs));
2196
2567
  if (typeof tool.outputBytes === "number" && tool.outputBytes > 0) parts.push(fmtBytes3(tool.outputBytes));
2197
2568
  if (typeof tool.outputLines === "number" && tool.outputLines > 0) parts.push(`${tool.outputLines}L`);
2198
2569
  return parts.join(" ");
@@ -2213,7 +2584,7 @@ function LiveActivityStrip({
2213
2584
  running.map((e) => {
2214
2585
  const toolElapsed = e.currentTool ? now - e.currentTool.startedAt : 0;
2215
2586
  const taskElapsed = now - e.startedAt;
2216
- const toolSeg = e.currentTool ? `\u2192 ${e.currentTool.name} (${fmtElapsed2(toolElapsed)})` : "idle between tools";
2587
+ const toolSeg = e.currentTool ? `\u2192 ${e.currentTool.name} (${fmtElapsed6(toolElapsed)})` : "idle between tools";
2217
2588
  const recentTools = (e.recentTools ?? []).slice(-2).map(fmtRecentTool2).join(" | ");
2218
2589
  const messageText = e.streamingText.trim() || (e.recentMessages ?? []).slice(-1).map(fmtRecentMessage2).join("");
2219
2590
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
@@ -2227,7 +2598,7 @@ function LiveActivityStrip({
2227
2598
  "it ",
2228
2599
  e.toolCalls,
2229
2600
  "tc \xB7 ",
2230
- fmtElapsed2(taskElapsed)
2601
+ fmtElapsed6(taskElapsed)
2231
2602
  ] }),
2232
2603
  recentTools ? /* @__PURE__ */ jsxs(Fragment, { children: [
2233
2604
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "|" }),
@@ -2386,10 +2757,10 @@ async function walk(root, rel, depth, out) {
2386
2757
  }
2387
2758
  }
2388
2759
  }
2389
- function score(s, query) {
2390
- if (!query) return s.length;
2760
+ function score(s2, query) {
2761
+ if (!query) return s2.length;
2391
2762
  const ql = query.toLowerCase();
2392
- const sl = s.toLowerCase();
2763
+ const sl = s2.toLowerCase();
2393
2764
  let si = 0;
2394
2765
  let firstHit = -1;
2395
2766
  let lastHit = -1;
@@ -2402,7 +2773,7 @@ function score(s, query) {
2402
2773
  si++;
2403
2774
  }
2404
2775
  const span = lastHit - firstHit;
2405
- return span * 100 + firstHit * 2 + s.length;
2776
+ return span * 100 + firstHit * 2 + s2.length;
2406
2777
  }
2407
2778
  async function searchFiles(root, query, limit = 8) {
2408
2779
  const all = await loadIndex(root);
@@ -2556,8 +2927,8 @@ function renderList(queue) {
2556
2927
  }
2557
2928
  return lines.join("\n");
2558
2929
  }
2559
- function oneLine(s, max) {
2560
- const collapsed = s.replace(/\s+/g, " ").trim();
2930
+ function oneLine(s2, max) {
2931
+ const collapsed = s2.replace(/\s+/g, " ").trim();
2561
2932
  return collapsed.length <= max ? collapsed : `${collapsed.slice(0, max - 1)}\u2026`;
2562
2933
  }
2563
2934
  var USAGE2 = "Usage:\n /kill \u2014 list active processes + breaker state\n /kill list \u2014 same as /kill\n /kill all \u2014 kill all tracked processes (SIGTERM \u2192 SIGKILL)\n /kill force \u2014 kill all with SIGKILL immediately\n /kill reset \u2014 reset the circuit breaker to closed\n /kill <pid> \u2014 kill a specific process by PID";
@@ -3224,6 +3595,89 @@ function reducer(state, action) {
3224
3595
  case "goalSummary": {
3225
3596
  return { ...state, goalSummary: action.summary };
3226
3597
  }
3598
+ case "autoPhaseInit": {
3599
+ return {
3600
+ ...state,
3601
+ autoPhase: {
3602
+ title: action.title,
3603
+ phases: {},
3604
+ runningPhaseIds: [],
3605
+ elapsedMs: 0,
3606
+ monitorOpen: false
3607
+ }
3608
+ };
3609
+ }
3610
+ case "autoPhasePhaseUpdate": {
3611
+ const existing = state.autoPhase ?? {
3612
+ title: "AutoPhase",
3613
+ phases: {},
3614
+ runningPhaseIds: [],
3615
+ elapsedMs: 0,
3616
+ monitorOpen: false
3617
+ };
3618
+ return {
3619
+ ...state,
3620
+ autoPhase: {
3621
+ ...existing,
3622
+ phases: {
3623
+ ...existing.phases,
3624
+ [action.phaseId]: {
3625
+ name: action.name,
3626
+ status: action.status,
3627
+ completedTasks: action.completedTasks,
3628
+ totalTasks: action.totalTasks,
3629
+ startedAt: action.startedAt
3630
+ }
3631
+ }
3632
+ }
3633
+ };
3634
+ }
3635
+ case "autoPhaseRunningPhases": {
3636
+ if (!state.autoPhase) return state;
3637
+ return {
3638
+ ...state,
3639
+ autoPhase: { ...state.autoPhase, runningPhaseIds: action.phaseIds }
3640
+ };
3641
+ }
3642
+ case "autoPhaseElapsed": {
3643
+ if (!state.autoPhase) return state;
3644
+ return { ...state, autoPhase: { ...state.autoPhase, elapsedMs: action.ms } };
3645
+ }
3646
+ case "autoPhaseMonitorToggle": {
3647
+ if (!state.autoPhase) return state;
3648
+ return { ...state, autoPhase: { ...state.autoPhase, monitorOpen: !state.autoPhase.monitorOpen } };
3649
+ }
3650
+ case "autoPhaseReset": {
3651
+ return { ...state, autoPhase: null };
3652
+ }
3653
+ case "worktreeUpsert": {
3654
+ const prev = state.worktrees[action.handleId];
3655
+ const merged = {
3656
+ branch: "",
3657
+ ownerLabel: "",
3658
+ status: "active",
3659
+ insertions: 0,
3660
+ deletions: 0,
3661
+ files: 0,
3662
+ allocatedAt: Date.now(),
3663
+ ...prev,
3664
+ ...action.row
3665
+ };
3666
+ return {
3667
+ ...state,
3668
+ worktrees: { ...state.worktrees, [action.handleId]: merged },
3669
+ worktreeBase: action.baseBranch ?? state.worktreeBase
3670
+ };
3671
+ }
3672
+ case "worktreeRemove": {
3673
+ if (!state.worktrees[action.handleId]) return state;
3674
+ const next = { ...state.worktrees };
3675
+ delete next[action.handleId];
3676
+ return { ...state, worktrees: next };
3677
+ }
3678
+ case "worktreeMonitorToggle": {
3679
+ return { ...state, worktreeMonitorOpen: !state.worktreeMonitorOpen };
3680
+ }
3227
3681
  }
3228
3682
  }
3229
3683
  var PASTE_THRESHOLD_CHARS = 200;
@@ -3234,7 +3688,7 @@ function buildSteeringPreamble(snapshot, newDirection) {
3234
3688
  ctx.push(`- in-flight tools (now cancelled): ${snapshot.runningTools.join(", ")}`);
3235
3689
  }
3236
3690
  if (snapshot?.subagentsTerminated && snapshot.subagentsTerminated > 0) {
3237
- const subDetails = snapshot.subagents.map((s) => `${s.label}${s.tool ? ` (was running: ${s.tool})` : ""}`).join(", ");
3691
+ const subDetails = snapshot.subagents.map((s2) => `${s2.label}${s2.tool ? ` (was running: ${s2.tool})` : ""}`).join(", ");
3238
3692
  ctx.push(
3239
3693
  `- subagents (${snapshot.subagentsTerminated} terminated by me, do NOT await them): ${subDetails}`
3240
3694
  );
@@ -3279,6 +3733,7 @@ function App({
3279
3733
  getParallelEngine,
3280
3734
  subscribeEternalIteration,
3281
3735
  subscribeEternalStage,
3736
+ subscribeAutoPhase,
3282
3737
  getSDDContext,
3283
3738
  onSDDOutput,
3284
3739
  appVersion,
@@ -3395,7 +3850,10 @@ function App({
3395
3850
  checkpoints: [],
3396
3851
  rewindOverlay: null,
3397
3852
  eternalStage: null,
3398
- goalSummary: null
3853
+ goalSummary: null,
3854
+ autoPhase: null,
3855
+ worktrees: {},
3856
+ worktreeMonitorOpen: false
3399
3857
  });
3400
3858
  const builderRef = useRef(null);
3401
3859
  if (builderRef.current === null) {
@@ -3794,9 +4252,9 @@ function App({
3794
4252
  if (!text) {
3795
4253
  return { message: "Usage: /steer <new direction>" };
3796
4254
  }
3797
- const s = stateRef.current;
3798
- const runningTools = Array.from(s.runningTools.values()).map((t) => t.name);
3799
- const subagents = Object.values(s.fleet).filter((e) => e.status === "running").map((e) => ({ label: e.name, status: e.status, tool: e.currentTool?.name }));
4255
+ const s2 = stateRef.current;
4256
+ const runningTools = Array.from(s2.runningTools.values()).map((t) => t.name);
4257
+ const subagents = Object.values(s2.fleet).filter((e) => e.status === "running").map((e) => ({ label: e.name, status: e.status, tool: e.currentTool?.name }));
3800
4258
  const subagentsTerminated = subagents.length;
3801
4259
  const partialAssistantText = streamingTextRef.current.slice(-1500);
3802
4260
  activeCtrlRef.current?.abort();
@@ -3804,7 +4262,7 @@ function App({
3804
4262
  type: "steerStart",
3805
4263
  snapshot: { runningTools, subagents, subagentsTerminated, partialAssistantText }
3806
4264
  });
3807
- const droppedCount = s.queue.length;
4265
+ const droppedCount = s2.queue.length;
3808
4266
  if (droppedCount > 0) dispatch({ type: "queueClear" });
3809
4267
  if (director && subagentsTerminated > 0) {
3810
4268
  const cap = new Promise((resolve) => {
@@ -3851,8 +4309,8 @@ function App({
3851
4309
  handleRewindTo(idx);
3852
4310
  return {};
3853
4311
  }
3854
- const s = stateRef.current;
3855
- if (s.checkpoints.length === 0) {
4312
+ const s2 = stateRef.current;
4313
+ if (s2.checkpoints.length === 0) {
3856
4314
  return { message: "No checkpoints in this session yet." };
3857
4315
  }
3858
4316
  dispatch({ type: "rewindOverlayOpen" });
@@ -4201,6 +4659,108 @@ function App({
4201
4659
  offRewound();
4202
4660
  };
4203
4661
  }, [events, onClearHistory]);
4662
+ useEffect(() => {
4663
+ if (!subscribeAutoPhase) return;
4664
+ const handler = (event, payload) => {
4665
+ switch (event) {
4666
+ case "phase.started": {
4667
+ const p = payload;
4668
+ dispatch({ type: "autoPhasePhaseUpdate", phaseId: p.phaseId, name: p.name, status: "running", completedTasks: 0, totalTasks: 0, startedAt: Date.now() });
4669
+ break;
4670
+ }
4671
+ case "phase.completed": {
4672
+ const p = payload;
4673
+ dispatch({ type: "autoPhasePhaseUpdate", phaseId: p.phaseId, name: p.name, status: "completed", completedTasks: 0, totalTasks: 0 });
4674
+ break;
4675
+ }
4676
+ case "phase.failed": {
4677
+ const p = payload;
4678
+ dispatch({ type: "autoPhasePhaseUpdate", phaseId: p.phaseId, name: p.name, status: "failed", completedTasks: 0, totalTasks: 0 });
4679
+ break;
4680
+ }
4681
+ case "phase.statusChange": {
4682
+ const p = payload;
4683
+ const status = p.to === "running" ? "running" : p.to;
4684
+ dispatch({ type: "autoPhasePhaseUpdate", phaseId: p.phaseId, name: p.name, status, completedTasks: 0, totalTasks: 0 });
4685
+ break;
4686
+ }
4687
+ case "phase.taskCompleted": {
4688
+ const p = payload;
4689
+ const existing = stateRef.current.autoPhase?.phases[p.phaseId];
4690
+ if (existing) {
4691
+ dispatch({
4692
+ type: "autoPhasePhaseUpdate",
4693
+ phaseId: p.phaseId,
4694
+ name: existing.name,
4695
+ status: existing.status,
4696
+ completedTasks: existing.completedTasks + 1,
4697
+ totalTasks: existing.totalTasks
4698
+ });
4699
+ }
4700
+ break;
4701
+ }
4702
+ case "autonomous.tick": {
4703
+ const p = payload;
4704
+ dispatch({ type: "autoPhaseRunningPhases", phaseIds: p.activePhases.map((ph) => ph.id) });
4705
+ const ap = stateRef.current.autoPhase;
4706
+ if (ap) {
4707
+ const firstPhase = ap.phases[Object.keys(ap.phases)[0] ?? ""];
4708
+ const elapsed = ap.elapsedMs > 0 ? ap.elapsedMs + 1e3 : Date.now() - (firstPhase?.startedAt ?? Date.now());
4709
+ dispatch({ type: "autoPhaseElapsed", ms: elapsed });
4710
+ }
4711
+ break;
4712
+ }
4713
+ case "graph.completed": {
4714
+ dispatch({ type: "autoPhaseReset" });
4715
+ break;
4716
+ }
4717
+ case "graph.failed": {
4718
+ dispatch({ type: "autoPhaseReset" });
4719
+ break;
4720
+ }
4721
+ case "worktree.allocated": {
4722
+ const p = payload;
4723
+ dispatch({
4724
+ type: "worktreeUpsert",
4725
+ handleId: p.handleId,
4726
+ baseBranch: p.baseBranch,
4727
+ row: { branch: p.branch, ownerLabel: p.ownerLabel, baseBranch: p.baseBranch, status: "active", allocatedAt: Date.now() }
4728
+ });
4729
+ break;
4730
+ }
4731
+ case "worktree.committed": {
4732
+ const p = payload;
4733
+ dispatch({
4734
+ type: "worktreeUpsert",
4735
+ handleId: p.handleId,
4736
+ row: { insertions: p.insertions, deletions: p.deletions, files: p.files, status: "committing" }
4737
+ });
4738
+ break;
4739
+ }
4740
+ case "worktree.merged": {
4741
+ const p = payload;
4742
+ dispatch({ type: "worktreeUpsert", handleId: p.handleId, row: { status: "merged" } });
4743
+ break;
4744
+ }
4745
+ case "worktree.conflict": {
4746
+ const p = payload;
4747
+ dispatch({ type: "worktreeUpsert", handleId: p.handleId, row: { status: "needs-review", conflictFiles: p.conflictFiles } });
4748
+ break;
4749
+ }
4750
+ case "worktree.failed": {
4751
+ const p = payload;
4752
+ dispatch({ type: "worktreeUpsert", handleId: p.handleId, row: { status: "failed" } });
4753
+ break;
4754
+ }
4755
+ case "worktree.released": {
4756
+ const p = payload;
4757
+ if (!p.kept) dispatch({ type: "worktreeRemove", handleId: p.handleId });
4758
+ break;
4759
+ }
4760
+ }
4761
+ };
4762
+ return subscribeAutoPhase(handler);
4763
+ }, [subscribeAutoPhase]);
4204
4764
  useEffect(() => {
4205
4765
  const offFired = events.on("compaction.fired", (e) => {
4206
4766
  const { level, tokens, load, maxContext: maxContext2, report } = e;
@@ -4302,16 +4862,16 @@ function App({
4302
4862
  streamFlushTimer = null;
4303
4863
  };
4304
4864
  const status = d.status();
4305
- for (const s of status.subagents) {
4306
- const meta = d.getSubagentMeta(s.id);
4865
+ for (const s2 of status.subagents) {
4866
+ const meta = d.getSubagentMeta(s2.id);
4307
4867
  dispatch({
4308
4868
  type: "fleetSpawn",
4309
- id: s.id,
4310
- name: meta?.name ?? s.name,
4869
+ id: s2.id,
4870
+ name: meta?.name ?? s2.name,
4311
4871
  provider: meta?.provider,
4312
4872
  model: meta?.model
4313
4873
  });
4314
- labelFor(s.id, meta?.name ?? s.name);
4874
+ labelFor(s2.id, meta?.name ?? s2.name);
4315
4875
  }
4316
4876
  dispatch({ type: "fleetCost", cost: d.snapshot().total.cost, input: d.snapshot().total.input, output: d.snapshot().total.output });
4317
4877
  const seen = new Set(Object.keys(status.subagents));
@@ -4726,6 +5286,14 @@ function App({
4726
5286
  }
4727
5287
  return;
4728
5288
  }
5289
+ if (key.ctrl && input === "p") {
5290
+ dispatch({ type: "autoPhaseMonitorToggle" });
5291
+ return;
5292
+ }
5293
+ if (key.ctrl && input === "t") {
5294
+ dispatch({ type: "worktreeMonitorToggle" });
5295
+ return;
5296
+ }
4729
5297
  if (state.autonomyPicker.open) {
4730
5298
  if (key.escape) {
4731
5299
  dispatch({ type: "autonomyPickerClose" });
@@ -4947,6 +5515,15 @@ function App({
4947
5515
  if (state.historyIndex > 0) dispatch({ type: "historyDown" });
4948
5516
  return;
4949
5517
  }
5518
+ if (key.ctrl && input === "p") {
5519
+ if (state.autoPhase) dispatch({ type: "autoPhaseMonitorToggle" });
5520
+ else {
5521
+ slashRegistry.dispatch("/autophase", agent.ctx).then((res) => {
5522
+ if (res?.message) dispatch({ type: "addEntry", entry: { kind: "info", text: res.message } });
5523
+ });
5524
+ }
5525
+ return;
5526
+ }
4950
5527
  if (key.ctrl && input === "a") {
4951
5528
  setDraft(buffer, 0);
4952
5529
  return;
@@ -5194,6 +5771,10 @@ function App({
5194
5771
  if (res?.message) {
5195
5772
  dispatch({ type: "addEntry", entry: { kind: "info", text: res.message } });
5196
5773
  }
5774
+ if (res?.metadata?.autoPhaseInit) {
5775
+ const m = res.metadata.autoPhaseInit;
5776
+ dispatch({ type: "autoPhaseInit", title: m.title });
5777
+ }
5197
5778
  const ctxModel = agent.ctx.model;
5198
5779
  if (ctxModel && ctxModel !== liveModel) setLiveModel(ctxModel);
5199
5780
  const ctxProviderId = agent.ctx.provider?.id;
@@ -5433,6 +6014,23 @@ User message:
5433
6014
  totalTokens: state.fleetTokens,
5434
6015
  nowTick
5435
6016
  }
6017
+ ) : state.autoPhase?.monitorOpen ? /* @__PURE__ */ jsx(
6018
+ PhaseMonitor,
6019
+ {
6020
+ phases: state.autoPhase.phases,
6021
+ runningPhaseIds: state.autoPhase.runningPhaseIds,
6022
+ elapsedMs: state.autoPhase.elapsedMs,
6023
+ nowTick,
6024
+ onClose: () => dispatch({ type: "autoPhaseMonitorToggle" })
6025
+ }
6026
+ ) : state.worktreeMonitorOpen ? /* @__PURE__ */ jsx(
6027
+ WorktreeMonitor,
6028
+ {
6029
+ worktrees: state.worktrees,
6030
+ baseBranch: state.worktreeBase,
6031
+ nowTick,
6032
+ onClose: () => dispatch({ type: "worktreeMonitorToggle" })
6033
+ }
5436
6034
  ) : state.monitorOpen ? /* @__PURE__ */ jsx(
5437
6035
  FleetMonitor,
5438
6036
  {
@@ -5441,7 +6039,16 @@ User message:
5441
6039
  totalTokens: state.fleetTokens,
5442
6040
  nowTick
5443
6041
  }
5444
- ) : director ? /* @__PURE__ */ jsx(FleetPanel, { entries: state.fleet, totalCost: state.fleetCost, roster: fleetRoster }) : null
6042
+ ) : director ? /* @__PURE__ */ jsx(FleetPanel, { entries: state.fleet, totalCost: state.fleetCost, roster: fleetRoster }) : null,
6043
+ state.autoPhase && !state.autoPhase.monitorOpen ? /* @__PURE__ */ jsx(
6044
+ PhasePanel,
6045
+ {
6046
+ phases: state.autoPhase.phases,
6047
+ runningPhaseIds: state.autoPhase.runningPhaseIds,
6048
+ nowTick
6049
+ }
6050
+ ) : null,
6051
+ Object.keys(state.worktrees).length > 0 && !state.worktreeMonitorOpen && !state.monitorOpen ? /* @__PURE__ */ jsx(WorktreePanel, { worktrees: state.worktrees, nowTick }) : null
5445
6052
  ] });
5446
6053
  }
5447
6054
  function renderRunningTools(running) {
@@ -5476,6 +6083,75 @@ function fmtTok3(n) {
5476
6083
  return `${(n / 1e6).toFixed(1)}M`;
5477
6084
  }
5478
6085
 
6086
+ // src/terminal-title.ts
6087
+ var SPINNER = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
6088
+ var setTitle = (s2) => `\x1B]0;${s2}\x07`;
6089
+ function shortModel(model) {
6090
+ if (!model) return "";
6091
+ const base = model.split("/").pop() ?? model;
6092
+ return base.replace(/-\d{8}$/, "").replace(/\[.*?\]$/, "");
6093
+ }
6094
+ function marquee(text, offset, width) {
6095
+ const padded = `${text} `;
6096
+ const start = offset % padded.length;
6097
+ return (padded + padded).slice(start, start + width);
6098
+ }
6099
+ function startTerminalTitle(opts) {
6100
+ const { stdout, events } = opts;
6101
+ if (process.env["WRONGSTACK_NO_TITLE"] === "1" || !stdout.isTTY) {
6102
+ return () => {
6103
+ };
6104
+ }
6105
+ const app = opts.appName ?? "WrongStack";
6106
+ const model = shortModel(opts.model);
6107
+ const idleAfter = opts.idleAfterMs ?? 3500;
6108
+ const suffix = model ? ` \xB7 ${model}` : "";
6109
+ let frame = 0;
6110
+ let scroll = 0;
6111
+ let phase = "idle";
6112
+ let toolName = "";
6113
+ let lastActivity = 0;
6114
+ const touch = (next, tool) => {
6115
+ phase = next;
6116
+ if (tool) toolName = tool;
6117
+ lastActivity = Date.now();
6118
+ };
6119
+ const offs = [
6120
+ events.on("iteration.started", () => touch("thinking")),
6121
+ events.on("provider.text_delta", () => touch("thinking")),
6122
+ events.on("provider.thinking_delta", () => touch("thinking")),
6123
+ events.on("tool.started", (e) => touch("tool", e.name ?? "tool")),
6124
+ events.on("tool.executed", () => touch("thinking"))
6125
+ ];
6126
+ const write = (s2) => {
6127
+ try {
6128
+ stdout.write(s2);
6129
+ } catch {
6130
+ }
6131
+ };
6132
+ const timer = setInterval(() => {
6133
+ frame = (frame + 1) % SPINNER.length;
6134
+ scroll += 1;
6135
+ if (lastActivity && Date.now() - lastActivity > idleAfter) phase = "idle";
6136
+ const sp = SPINNER[frame];
6137
+ let title;
6138
+ if (phase === "tool") {
6139
+ title = `${sp} \u25B8 ${toolName}${suffix}`;
6140
+ } else if (phase === "thinking") {
6141
+ title = `${sp} thinking\u2026${suffix}`;
6142
+ } else {
6143
+ title = marquee(`\u2726 ${app}${suffix}`, scroll >> 1, 22);
6144
+ }
6145
+ write(setTitle(title));
6146
+ }, opts.intervalMs ?? 130);
6147
+ timer.unref?.();
6148
+ return () => {
6149
+ clearInterval(timer);
6150
+ for (const off of offs) off();
6151
+ write(setTitle(model ? `${app} \xB7 ${model}` : app));
6152
+ };
6153
+ }
6154
+
5479
6155
  // src/run-tui.ts
5480
6156
  var BRACKETED_PASTE_ON = "\x1B[?2004h";
5481
6157
  var BRACKETED_PASTE_OFF = "\x1B[?2004l";
@@ -5497,12 +6173,13 @@ async function runTui(opts) {
5497
6173
  stdout.write(CURSOR_HOME);
5498
6174
  }
5499
6175
  stdout.write(BRACKETED_PASTE_ON);
6176
+ const stopTitle = startTerminalTitle({ stdout, events: opts.events, model: opts.model });
5500
6177
  const swallowSignals = ["SIGTSTP", "SIGQUIT", "SIGTTIN", "SIGTTOU"];
5501
6178
  const swallow = () => {
5502
6179
  };
5503
- for (const s of swallowSignals) {
6180
+ for (const s2 of swallowSignals) {
5504
6181
  try {
5505
- process.on(s, swallow);
6182
+ process.on(s2, swallow);
5506
6183
  } catch {
5507
6184
  }
5508
6185
  }
@@ -5510,6 +6187,10 @@ async function runTui(opts) {
5510
6187
  const cleanup = () => {
5511
6188
  if (cleaned) return;
5512
6189
  cleaned = true;
6190
+ try {
6191
+ stopTitle();
6192
+ } catch {
6193
+ }
5513
6194
  try {
5514
6195
  stdout.write(BRACKETED_PASTE_OFF);
5515
6196
  if (useAltScreen) {
@@ -5521,13 +6202,13 @@ async function runTui(opts) {
5521
6202
  const signals = ["SIGTERM", "SIGHUP", "SIGINT"];
5522
6203
  const signalHandler = () => cleanup();
5523
6204
  const exitHandler = () => cleanup();
5524
- for (const s of signals) process.on(s, signalHandler);
6205
+ for (const s2 of signals) process.on(s2, signalHandler);
5525
6206
  process.on("exit", exitHandler);
5526
6207
  const detachListeners = () => {
5527
- for (const s of signals) process.off(s, signalHandler);
5528
- for (const s of swallowSignals) {
6208
+ for (const s2 of signals) process.off(s2, signalHandler);
6209
+ for (const s2 of swallowSignals) {
5529
6210
  try {
5530
- process.off(s, swallow);
6211
+ process.off(s2, swallow);
5531
6212
  } catch {
5532
6213
  }
5533
6214
  }
@@ -5570,6 +6251,7 @@ async function runTui(opts) {
5570
6251
  getParallelEngine: opts.getParallelEngine,
5571
6252
  subscribeEternalIteration: opts.subscribeEternalIteration,
5572
6253
  subscribeEternalStage: opts.subscribeEternalStage,
6254
+ subscribeAutoPhase: opts.subscribeAutoPhase,
5573
6255
  appVersion: opts.appVersion,
5574
6256
  provider: opts.provider,
5575
6257
  family: opts.family,