@prestyj/boss 4.3.160 → 4.3.164

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/README.md +3 -3
  2. package/dist/{chunk-P2JWHLOI.js → chunk-7QDS2KEP.js} +3 -3
  3. package/dist/{chunk-YNWFCUMR.js → chunk-DZO3FVYX.js} +3 -3
  4. package/dist/chunk-DZO3FVYX.js.map +1 -0
  5. package/dist/{chunk-7QT7YZKN.js → chunk-RVAJYH5L.js} +2 -2
  6. package/dist/{chunk-PGC5XQTC.js → chunk-STUHLGRN.js} +28536 -13365
  7. package/dist/{chunk-PGC5XQTC.js.map → chunk-STUHLGRN.js.map} +1 -1
  8. package/dist/cli.js +1705 -1000
  9. package/dist/cli.js.map +1 -1
  10. package/dist/{devtools-4TI4D7F2.js → devtools-ASTBUBZC.js} +11 -33
  11. package/dist/{devtools-4TI4D7F2.js.map → devtools-ASTBUBZC.js.map} +1 -1
  12. package/dist/{dist-VXOVSHZ5.js → dist-IGN2W3JX.js} +2 -2
  13. package/dist/{chunk-EZYGVECW.js → ignore-3XU7YNRW.js} +3 -6
  14. package/dist/index.js +4 -6
  15. package/dist/index.js.map +1 -1
  16. package/dist/{chunk-RMSZMSH5.js → out-NHVJUVVH.js} +3 -6
  17. package/dist/{pixel-55DE4AGI.js → pixel-ALJG63HU.js} +4 -4
  18. package/dist/{pixel-fix-4LGSDR3J.js → pixel-fix-6NKZMYMY.js} +3 -3
  19. package/package.json +5 -5
  20. package/dist/chunk-YNWFCUMR.js.map +0 -1
  21. package/dist/ignore-AXNNXJD4.js +0 -7
  22. package/dist/out-NH6HQBFM.js +0 -7
  23. package/dist/out-NH6HQBFM.js.map +0 -1
  24. package/dist/pixel-55DE4AGI.js.map +0 -1
  25. /package/dist/{chunk-P2JWHLOI.js.map → chunk-7QDS2KEP.js.map} +0 -0
  26. /package/dist/{chunk-7QT7YZKN.js.map → chunk-RVAJYH5L.js.map} +0 -0
  27. /package/dist/{dist-VXOVSHZ5.js.map → dist-IGN2W3JX.js.map} +0 -0
  28. /package/dist/{chunk-EZYGVECW.js.map → ignore-3XU7YNRW.js.map} +0 -0
  29. /package/dist/{chunk-RMSZMSH5.js.map → out-NHVJUVVH.js.map} +0 -0
  30. /package/dist/{ignore-AXNNXJD4.js.map → pixel-ALJG63HU.js.map} +0 -0
  31. /package/dist/{pixel-fix-4LGSDR3J.js.map → pixel-fix-6NKZMYMY.js.map} +0 -0
package/dist/cli.js CHANGED
@@ -1,32 +1,43 @@
1
1
  #!/usr/bin/env -S node --max-old-space-size=8192 --expose-gc
2
2
  import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
3
3
  import {
4
- ActivityIndicator,
5
4
  AnimationProvider,
6
5
  AssistantMessage,
7
6
  Box_default,
7
+ ChatControls,
8
+ ChatInputStack,
9
+ ChatLayout,
10
+ ChatLivePane,
8
11
  CompactionDone,
9
12
  CompactionSpinner,
10
13
  GGBoss,
11
14
  InputArea,
12
15
  MODELS,
13
16
  MessageResponse,
14
- ModelSelector,
15
- SelectList,
16
- Static,
17
- StreamingArea,
17
+ RESPONSE_LEFT_PADDING,
18
18
  TerminalSizeProvider,
19
19
  Text,
20
20
  ThemeContext,
21
21
  ToolExecution,
22
+ ToolGroupExecution,
22
23
  ToolUseLoader,
24
+ TranscriptItemFrame,
23
25
  UserMessage,
24
26
  bossStore,
27
+ bossToolGroupRenderers,
28
+ buildToolGroupSummary,
25
29
  closeLogger,
30
+ color,
31
+ dim,
32
+ formatHistoryWrite,
26
33
  getAppPaths,
27
34
  getBossState,
28
35
  getContextWindow,
36
+ getNextThinkingLevel,
29
37
  getSplashAudioDurationMs,
38
+ getTranscriptItemMarginTop,
39
+ gradientLine,
40
+ indent,
30
41
  initLogger,
31
42
  loadSettings,
32
43
  loadTheme,
@@ -36,9 +47,16 @@ import {
36
47
  require_jsx_runtime,
37
48
  require_react,
38
49
  saveSettings,
50
+ serializeCompletedItemToTerminalHistory,
39
51
  setStreamDiagnostic,
52
+ shouldSeparateTranscriptItemKinds,
53
+ shouldTopSpaceStreamingAssistant,
54
+ stripAnsi,
55
+ stripTerminalFocusSequences,
40
56
  subscribeToBossStore,
41
57
  tasksStore,
58
+ toolTonePalette,
59
+ truncatePlain,
42
60
  useAnimationActive,
43
61
  useAnimationTick,
44
62
  useBossState,
@@ -48,18 +66,17 @@ import {
48
66
  useTheme,
49
67
  use_app_default,
50
68
  use_input_default,
51
- use_stdout_default
52
- } from "./chunk-PGC5XQTC.js";
53
- import "./chunk-RMSZMSH5.js";
54
- import "./chunk-EZYGVECW.js";
55
- import "./chunk-P2JWHLOI.js";
69
+ use_stdout_default,
70
+ wrapPlain
71
+ } from "./chunk-STUHLGRN.js";
72
+ import "./chunk-7QDS2KEP.js";
56
73
  import {
57
74
  source_default
58
- } from "./chunk-7QT7YZKN.js";
75
+ } from "./chunk-RVAJYH5L.js";
59
76
  import {
60
77
  __toESM,
61
78
  init_esm_shims
62
- } from "./chunk-YNWFCUMR.js";
79
+ } from "./chunk-DZO3FVYX.js";
63
80
 
64
81
  // src/cli.ts
65
82
  init_esm_shims();
@@ -344,7 +361,7 @@ init_esm_shims();
344
361
  // package.json
345
362
  var package_default = {
346
363
  name: "@prestyj/boss",
347
- version: "4.3.160",
364
+ version: "4.3.164",
348
365
  type: "module",
349
366
  description: "Orchestrator agent that drives multiple ezcoder sessions across projects from a single chat",
350
367
  license: "MIT",
@@ -374,7 +391,7 @@ var package_default = {
374
391
  "@types/node": "^25.6.0",
375
392
  "@types/react": "^19.2.14",
376
393
  chalk: "^5.6.2",
377
- ink: "^7.0.2",
394
+ ink: "6.8.0",
378
395
  react: "^19.2.5",
379
396
  tsup: "^8.5.1",
380
397
  typescript: "^6.0.3",
@@ -422,24 +439,6 @@ var GRADIENT = [
422
439
  "#b91c1c"
423
440
  // red-700 (slight darker tail)
424
441
  ];
425
- var PULSE_COLORS = [
426
- "#dc2626",
427
- // crimson
428
- "#e11d48",
429
- // rose
430
- "#be185d",
431
- // wine
432
- "#a21caf",
433
- // magenta
434
- "#c026d3",
435
- // fuchsia
436
- "#a21caf",
437
- // back
438
- "#be185d",
439
- // back
440
- "#e11d48"
441
- // back
442
- ];
443
442
  var COLORS = {
444
443
  primary: "#e11d48",
445
444
  // crimson-rose — main brand color
@@ -452,7 +451,7 @@ var COLORS = {
452
451
  error: "#f87171"
453
452
  };
454
453
  function clearScreen() {
455
- process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
454
+ process.stdout.write("\x1B[2J\x1B[H");
456
455
  }
457
456
 
458
457
  // src/banner.tsx
@@ -502,9 +501,9 @@ function GradientText({ text }) {
502
501
  if (ch === " ") {
503
502
  chars.push(ch);
504
503
  } else {
505
- const color = GRADIENT[colorIdx % GRADIENT.length];
504
+ const color2 = GRADIENT[colorIdx % GRADIENT.length];
506
505
  chars.push(
507
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color, children: ch }, i)
506
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: color2, children: ch }, i)
508
507
  );
509
508
  colorIdx++;
510
509
  }
@@ -1001,8 +1000,14 @@ async function saveBossTelegramConfig(config) {
1001
1000
  }
1002
1001
  function formatItemForTelegram(item) {
1003
1002
  switch (item.kind) {
1003
+ case "banner":
1004
1004
  case "user":
1005
- case "tool":
1005
+ case "tool_start":
1006
+ case "tool_done":
1007
+ case "tool_group":
1008
+ case "compacting":
1009
+ case "compacted":
1010
+ case "stopped":
1006
1011
  case "worker_event":
1007
1012
  return null;
1008
1013
  case "assistant": {
@@ -1341,7 +1346,7 @@ ${lines.join("\n")}`);
1341
1346
  await bot.send(chatId, `_Voice transcription failed: ${message}_${hint}`);
1342
1347
  }
1343
1348
  });
1344
- process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
1349
+ process.stdout.write("\x1B[2J\x1B[H");
1345
1350
  printBanner({
1346
1351
  bossModel: options.bossModel,
1347
1352
  workerModel: options.workerModel,
@@ -1445,7 +1450,7 @@ function printBanner(opts) {
1445
1450
  init_esm_shims();
1446
1451
  import readline2 from "readline/promises";
1447
1452
  async function runBossTelegramSetup() {
1448
- process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
1453
+ process.stdout.write("\x1B[2J\x1B[H");
1449
1454
  printSetupBanner();
1450
1455
  const existing = await loadBossTelegramConfig();
1451
1456
  if (existing) {
@@ -1533,7 +1538,7 @@ function printSetupBanner() {
1533
1538
 
1534
1539
  // src/orchestrator-app.tsx
1535
1540
  init_esm_shims();
1536
- var import_react10 = __toESM(require_react(), 1);
1541
+ var import_react15 = __toESM(require_react(), 1);
1537
1542
 
1538
1543
  // ../cli/dist/ui/components/index.js
1539
1544
  init_esm_shims();
@@ -1543,29 +1548,100 @@ init_esm_shims();
1543
1548
  var import_jsx_runtime3 = __toESM(require_jsx_runtime(), 1);
1544
1549
  var import_react3 = __toESM(require_react(), 1);
1545
1550
 
1546
- // ../cli/dist/ui/components/Overlay.js
1551
+ // ../cli/dist/ui/components/SelectList.js
1547
1552
  init_esm_shims();
1548
1553
  var import_jsx_runtime4 = __toESM(require_jsx_runtime(), 1);
1549
1554
  var import_react4 = __toESM(require_react(), 1);
1555
+ function SelectList({ items, onSelect, onCancel, initialIndex = 0, windowSize }) {
1556
+ const theme = useTheme();
1557
+ const [selectedIndex, setSelectedIndex] = (0, import_react4.useState)(initialIndex);
1558
+ const [filter, setFilter] = (0, import_react4.useState)("");
1559
+ const filtered = (0, import_react4.useMemo)(() => {
1560
+ if (!filter)
1561
+ return items;
1562
+ const lower = filter.toLowerCase();
1563
+ return items.filter((item) => item.label.toLowerCase().includes(lower) || item.value.toLowerCase().includes(lower));
1564
+ }, [items, filter]);
1565
+ use_input_default((input, key) => {
1566
+ const inputWithoutFocusReports = stripTerminalFocusSequences(input);
1567
+ if (!inputWithoutFocusReports && input)
1568
+ return;
1569
+ input = inputWithoutFocusReports;
1570
+ if (key.escape) {
1571
+ onCancel();
1572
+ return;
1573
+ }
1574
+ if (key.return) {
1575
+ if (filtered.length > 0) {
1576
+ onSelect(filtered[selectedIndex].value);
1577
+ }
1578
+ return;
1579
+ }
1580
+ if (key.upArrow) {
1581
+ setSelectedIndex((i) => Math.max(0, i - 1));
1582
+ return;
1583
+ }
1584
+ if (key.downArrow) {
1585
+ setSelectedIndex((i) => Math.min(filtered.length - 1, i + 1));
1586
+ return;
1587
+ }
1588
+ if (key.backspace || key.delete) {
1589
+ setFilter((f) => f.slice(0, -1));
1590
+ setSelectedIndex(0);
1591
+ return;
1592
+ }
1593
+ if (input && !key.ctrl && !key.meta) {
1594
+ setFilter((f) => f + input);
1595
+ setSelectedIndex(0);
1596
+ }
1597
+ });
1598
+ const total = filtered.length;
1599
+ const clampedIndex = Math.min(Math.max(selectedIndex, 0), Math.max(0, total - 1));
1600
+ const useWindow = windowSize !== void 0 && windowSize > 0 && total > windowSize;
1601
+ const start = useWindow ? Math.max(0, Math.min(clampedIndex - Math.floor(windowSize / 2), total - windowSize)) : 0;
1602
+ const end = useWindow ? Math.min(start + windowSize, total) : total;
1603
+ const visible = useWindow ? filtered.slice(start, end) : filtered;
1604
+ const hasAbove = useWindow && start > 0;
1605
+ const hasBelow = useWindow && end < total;
1606
+ return (0, import_jsx_runtime4.jsxs)(Box_default, { flexDirection: "column", children: [filter && (0, import_jsx_runtime4.jsx)(Box_default, { marginBottom: 1, children: (0, import_jsx_runtime4.jsxs)(Text, { color: theme.textDim, children: ["Filter: ", filter] }) }), hasAbove && (0, import_jsx_runtime4.jsxs)(Text, { color: theme.textDim, children: [" \u2191 ", start, " more"] }), visible.map((item, i) => {
1607
+ const index = useWindow ? start + i : i;
1608
+ return (0, import_jsx_runtime4.jsxs)(Box_default, { children: [(0, import_jsx_runtime4.jsxs)(Text, { color: index === clampedIndex ? theme.primary : theme.text, children: [index === clampedIndex ? "\u276F " : " ", item.label] }), item.description && (0, import_jsx_runtime4.jsxs)(Text, { color: theme.textDim, children: [" \u2014 ", item.description] })] }, item.value);
1609
+ }), hasBelow && (0, import_jsx_runtime4.jsxs)(Text, { color: theme.textDim, children: [" \u2193 ", total - end, " more"] }), filtered.length === 0 && (0, import_jsx_runtime4.jsx)(Text, { color: theme.textDim, children: "No matches" }), (0, import_jsx_runtime4.jsx)(Box_default, { marginTop: 1, children: (0, import_jsx_runtime4.jsx)(Text, { color: theme.textDim, children: "\u2191\u2193 navigate \xB7 Enter select \xB7 Esc cancel" }) })] });
1610
+ }
1550
1611
 
1551
- // ../cli/dist/ui/components/SessionSelector.js
1612
+ // ../cli/dist/ui/components/TaskOverlay.js
1552
1613
  init_esm_shims();
1553
1614
  var import_jsx_runtime5 = __toESM(require_jsx_runtime(), 1);
1554
1615
  var import_react5 = __toESM(require_react(), 1);
1616
+ import { randomUUID } from "crypto";
1617
+ import { homedir } from "os";
1618
+ var GAP = " ";
1619
+ var LOGO_WIDTH = 9;
1620
+ var SIDE_BY_SIDE_MIN = LOGO_WIDTH + GAP.length + 20;
1555
1621
 
1556
- // ../cli/dist/ui/components/SettingsSelector.js
1622
+ // ../cli/dist/ui/components/SessionSelector.js
1557
1623
  init_esm_shims();
1558
1624
  var import_jsx_runtime6 = __toESM(require_jsx_runtime(), 1);
1559
1625
  var import_react6 = __toESM(require_react(), 1);
1560
1626
 
1561
- // src/boss-footer.tsx
1627
+ // ../cli/dist/ui/components/SettingsSelector.js
1562
1628
  init_esm_shims();
1563
- var import_react7 = __toESM(require_react(), 1);
1564
1629
  var import_jsx_runtime7 = __toESM(require_jsx_runtime(), 1);
1630
+ var import_react7 = __toESM(require_react(), 1);
1631
+
1632
+ // src/boss-chat-screen.tsx
1633
+ init_esm_shims();
1634
+ var import_react13 = __toESM(require_react(), 1);
1635
+
1636
+ // src/boss-footer.tsx
1637
+ init_esm_shims();
1638
+ var import_react8 = __toESM(require_react(), 1);
1639
+ var import_jsx_runtime8 = __toESM(require_jsx_runtime(), 1);
1565
1640
  var PARTIAL_BLOCKS = [" ", "\u258F", "\u258E", "\u258D", "\u258C", "\u258B", "\u258A", "\u2589", "\u2588"];
1566
1641
  var LIGHT_SHADE = "\u2591";
1642
+ var BAR_WIDTH = 8;
1567
1643
  var SHORT_MODELS = {
1568
- "claude-opus-4-7": "Opus",
1644
+ "claude-opus-4-8": "Opus",
1569
1645
  "claude-sonnet-4-6": "Sonnet",
1570
1646
  "claude-haiku-4-5": "Haiku",
1571
1647
  "claude-haiku-4-5-20251001": "Haiku",
@@ -1577,17 +1653,51 @@ var SHORT_MODELS = {
1577
1653
  function shortModel(model) {
1578
1654
  return SHORT_MODELS[model] ?? model;
1579
1655
  }
1580
- function getContextPercent(model, tokensIn) {
1656
+ function getBossFooterContextPercent(model, tokensIn) {
1581
1657
  const limit = getContextWindow(model);
1582
1658
  if (!limit || tokensIn === 0) return 0;
1583
1659
  return Math.round(tokensIn / limit * 100);
1584
1660
  }
1661
+ function getContextColor(pct, theme) {
1662
+ if (pct >= 80) return theme.error;
1663
+ if (pct >= 50) return theme.warning;
1664
+ return theme.success;
1665
+ }
1666
+ function getThinkingColor(level, theme) {
1667
+ if (!level) return theme.textDim;
1668
+ if (level === "low") return theme.textMuted;
1669
+ if (level === "medium") return theme.accent;
1670
+ if (level === "high") return theme.warning;
1671
+ return COLORS.accent;
1672
+ }
1673
+ function getBossFooterThinkingLabel(level) {
1674
+ return level ? `Thinking ${level}` : "Thinking off";
1675
+ }
1585
1676
  var SHORT_RADIO = {
1586
1677
  "somafm-groove-salad": "Groove Salad",
1587
1678
  "somafm-drone-zone": "Drone Zone",
1588
1679
  "radio-paradise": "Radio Paradise",
1589
1680
  "george-fm": "George FM"
1590
1681
  };
1682
+ function renderContextBar({
1683
+ contextPct,
1684
+ contextColor,
1685
+ dimColor
1686
+ }) {
1687
+ const fillFloat = Math.min(contextPct / 100 * BAR_WIDTH, BAR_WIDTH);
1688
+ const barChars = [];
1689
+ for (let i = 0; i < BAR_WIDTH; i++) {
1690
+ const cellFill = Math.max(0, Math.min(1, fillFloat - i));
1691
+ const eighths = Math.round(cellFill * 8);
1692
+ barChars.push(
1693
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: eighths > 0 ? contextColor : dimColor, children: eighths > 0 ? PARTIAL_BLOCKS[eighths] : LIGHT_SHADE }, i)
1694
+ );
1695
+ }
1696
+ return barChars;
1697
+ }
1698
+ function getBossFooterScopeLabel(scope) {
1699
+ return scope === "all" ? "all projects" : scope;
1700
+ }
1591
1701
  function BossFooter({
1592
1702
  bossModel,
1593
1703
  workerModel,
@@ -1595,149 +1705,231 @@ function BossFooter({
1595
1705
  exitPending,
1596
1706
  bossThinkingLevel,
1597
1707
  updatePending,
1598
- currentRadioStationId
1708
+ currentRadioStationId,
1709
+ scope
1599
1710
  }) {
1600
1711
  const theme = useTheme();
1601
1712
  const { columns } = useTerminalSize();
1602
1713
  if (exitPending) {
1603
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Box_default, { paddingX: 1, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: theme.warning, children: "Press Ctrl+C again to exit" }) });
1604
- }
1605
- const contextPct = getContextPercent(bossModel, tokensIn);
1606
- const contextColor = contextPct >= 80 ? theme.error : contextPct >= 50 ? theme.warning : theme.success;
1607
- const sep = /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: theme.border, children: " \u2502 " });
1608
- const barWidth = 8;
1609
- const fillFloat = Math.min(contextPct / 100 * barWidth, barWidth);
1610
- const barChars = [];
1611
- for (let i = 0; i < barWidth; i++) {
1612
- const cellFill = Math.max(0, Math.min(1, fillFloat - i));
1613
- const eighths = Math.round(cellFill * 8);
1614
- if (eighths === 8) {
1615
- barChars.push(
1616
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: contextColor, children: PARTIAL_BLOCKS[8] }, i)
1617
- );
1618
- } else if (eighths > 0) {
1619
- barChars.push(
1620
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: contextColor, children: PARTIAL_BLOCKS[eighths] }, i)
1621
- );
1622
- } else {
1623
- barChars.push(
1624
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: theme.textDim, children: LIGHT_SHADE }, i)
1625
- );
1626
- }
1714
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box_default, { paddingLeft: 1, paddingRight: 1, width: columns, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.warning, children: "Press Ctrl+C again to exit" }) });
1627
1715
  }
1716
+ const contextPct = getBossFooterContextPercent(bossModel, tokensIn);
1717
+ const contextColor = getContextColor(contextPct, theme);
1718
+ const sep = /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.border, children: " \u2502 " });
1719
+ const bossName = shortModel(bossModel);
1720
+ const workerName = shortModel(workerModel);
1721
+ const thinkingText = getBossFooterThinkingLabel(bossThinkingLevel);
1628
1722
  const radioName = currentRadioStationId ? SHORT_RADIO[currentRadioStationId] ?? currentRadioStationId : null;
1629
- const bossM = shortModel(bossModel);
1630
- const wkrM = shortModel(workerModel);
1631
- const estFull = 2 + 12 + // bar + " 99%"
1632
- 3 + 5 + bossM.length + // " │ boss <model>"
1633
- 3 + 8 + wkrM.length + // " │ workers <model>"
1634
- 3 + 12 + // " │ Thinking off"
1635
- (radioName ? 3 + 2 + radioName.length : 0) + // " │ ♪ Name"
1636
- (updatePending ? 3 + 28 : 0);
1637
- const dropLabels = estFull > columns;
1638
- const dropThinking = estFull > columns + 14;
1639
- const useShortUpdate = updatePending && estFull > columns + 6;
1640
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Box_default, { paddingX: 1, width: columns, children: [
1641
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Box_default, { flexGrow: 1 }),
1642
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Box_default, { flexShrink: 0, children: [
1643
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { children: barChars }),
1644
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Text, { color: contextColor, children: [
1645
- " ",
1646
- contextPct,
1647
- "%"
1648
- ] }),
1649
- sep,
1650
- !dropLabels && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: theme.textDim, children: "boss " }),
1651
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: COLORS.primary, bold: true, children: bossM }),
1723
+ const updateText = updatePending ? "Update ready. Restart EZ Boss." : null;
1724
+ const leftText = getBossFooterScopeLabel(scope);
1725
+ const barChars = renderContextBar({
1726
+ contextPct,
1727
+ contextColor,
1728
+ dimColor: theme.textDim
1729
+ });
1730
+ const rightLen = BAR_WIDTH + 1 + String(contextPct).length + 3 + bossName.length + 3 + "workers ".length + workerName.length + 3 + thinkingText.length + (radioName ? 3 + 2 + radioName.length : 0) + (updateText ? 3 + updateText.length : 0);
1731
+ const availableWidth = columns - 2;
1732
+ const fitsOnOneLine = leftText.length + rightLen <= availableWidth;
1733
+ const hideRadio = !!radioName && leftText.length + rightLen > availableWidth + 8;
1734
+ const compactUpdate = !!updateText && leftText.length + rightLen > availableWidth + 12;
1735
+ const rightContent = /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
1736
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { children: barChars }),
1737
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { color: contextColor, children: [
1738
+ " ",
1739
+ contextPct,
1740
+ "%"
1741
+ ] }),
1742
+ sep,
1743
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.primary, bold: true, children: bossName }),
1744
+ sep,
1745
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.textDim, children: "workers " }),
1746
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: COLORS.accent, bold: true, children: workerName }),
1747
+ sep,
1748
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: getThinkingColor(bossThinkingLevel, theme), bold: bossThinkingLevel === "high", children: thinkingText }),
1749
+ radioName && !hideRadio && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
1652
1750
  sep,
1653
- !dropLabels && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: theme.textDim, children: "workers " }),
1654
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: COLORS.accent, bold: true, children: wkrM }),
1655
- !dropThinking && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
1656
- sep,
1657
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: bossThinkingLevel ? theme.accent : theme.textDim, children: bossThinkingLevel ? "Thinking on" : "Thinking off" })
1658
- ] }),
1659
- radioName && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
1660
- sep,
1661
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Text, { color: theme.secondary ?? theme.accent, children: [
1662
- "\u266A ",
1663
- radioName
1664
- ] })
1665
- ] }),
1666
- updatePending && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
1667
- sep,
1668
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: theme.success, bold: true, wrap: "truncate", children: useShortUpdate ? "Update ready" : "Update ready. Restart EZ Boss." })
1751
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { color: theme.secondary, children: [
1752
+ "\u266A ",
1753
+ radioName
1669
1754
  ] })
1755
+ ] }),
1756
+ updateText && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
1757
+ sep,
1758
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.success, bold: true, wrap: "truncate", children: compactUpdate ? "Update ready" : updateText })
1670
1759
  ] })
1671
1760
  ] });
1761
+ if (fitsOnOneLine) {
1762
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box_default, { paddingLeft: 1, paddingRight: 1, width: columns, children: [
1763
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box_default, { flexGrow: 1, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.textDim, wrap: "truncate", children: leftText }) }),
1764
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box_default, { flexShrink: 0, children: rightContent })
1765
+ ] });
1766
+ }
1767
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box_default, { flexDirection: "column", paddingLeft: 1, paddingRight: 1, width: columns, children: [
1768
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box_default, { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.textDim, wrap: "truncate", children: leftText }) }),
1769
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box_default, { children: rightContent })
1770
+ ] });
1672
1771
  }
1673
1772
 
1674
- // src/slash-commands.ts
1773
+ // src/boss-model-selector.tsx
1675
1774
  init_esm_shims();
1676
- var BOSS_SLASH_COMMANDS = [
1677
- { name: "help", aliases: ["?"], description: "Show available commands" },
1678
- { name: "model-boss", aliases: [], description: "Switch the orchestrator's model" },
1679
- { name: "model-workers", aliases: [], description: "Switch every worker's model" },
1680
- { name: "compact", aliases: [], description: "Compact the boss's context now" },
1681
- { name: "clear", aliases: [], description: "Clear chat history and terminal" },
1682
- { name: "radio", aliases: [], description: "Stream a free internet radio station" },
1683
- { name: "quit", aliases: ["q", "exit"], description: "Exit ezboss" }
1684
- ];
1685
- function isSlashCommand(value) {
1686
- return value.startsWith("/") && !value.startsWith("//");
1687
- }
1688
- function parseSlash(value) {
1689
- if (!isSlashCommand(value)) return null;
1690
- const rest = value.slice(1).trim();
1691
- if (!rest) return null;
1692
- const space = rest.indexOf(" ");
1693
- if (space === -1) return { name: rest.toLowerCase(), args: "" };
1694
- return { name: rest.slice(0, space).toLowerCase(), args: rest.slice(space + 1).trim() };
1695
- }
1696
- function canonicalName(name) {
1697
- for (const cmd of BOSS_SLASH_COMMANDS) {
1698
- if (cmd.name === name) return cmd.name;
1699
- if (cmd.aliases.includes(name)) return cmd.name;
1775
+ var import_react9 = __toESM(require_react(), 1);
1776
+ var import_jsx_runtime9 = __toESM(require_jsx_runtime(), 1);
1777
+ var MAX_MODELS_TO_SHOW = 8;
1778
+ var PROVIDER_LABEL = {
1779
+ anthropic: "Anthropic",
1780
+ openai: "OpenAI",
1781
+ gemini: "Gemini",
1782
+ glm: "Z.AI",
1783
+ moonshot: "Moonshot",
1784
+ xiaomi: "Xiaomi",
1785
+ minimax: "MiniMax",
1786
+ deepseek: "DeepSeek",
1787
+ openrouter: "OpenRouter"
1788
+ };
1789
+ var ESC = String.fromCharCode(27);
1790
+ var ESC_FOCUS_GAINED = `${ESC}[I`;
1791
+ var ESC_FOCUS_LOST = `${ESC}[O`;
1792
+ var ESC_LESS_FOCUS_GAINED = "[I";
1793
+ var ESC_LESS_FOCUS_LOST = "[O";
1794
+ function stripTerminalFocusSequences2(input) {
1795
+ const withoutEscFocusReports = input.replaceAll(ESC_FOCUS_GAINED, "").replaceAll(ESC_FOCUS_LOST, "");
1796
+ let remaining = withoutEscFocusReports;
1797
+ while (remaining.length > 0) {
1798
+ if (remaining.startsWith(ESC_LESS_FOCUS_GAINED) || remaining.startsWith(ESC_LESS_FOCUS_LOST)) {
1799
+ remaining = remaining.slice(2);
1800
+ continue;
1801
+ }
1802
+ return withoutEscFocusReports;
1700
1803
  }
1701
- return null;
1804
+ return "";
1702
1805
  }
1703
- function buildHelpText() {
1704
- const lines = ["**ezboss commands**", ""];
1705
- for (const cmd of BOSS_SLASH_COMMANDS) {
1706
- const aliases = cmd.aliases.length > 0 ? ` (${cmd.aliases.map((a) => "/" + a).join(", ")})` : "";
1707
- lines.push(`- \`/${cmd.name}\`${aliases} \u2014 ${cmd.description}`);
1708
- }
1709
- lines.push("");
1710
- lines.push("**Global keybindings**");
1711
- lines.push("- `Ctrl+T` \u2014 open the Tasks pane");
1712
- lines.push("- `Tab` \u2014 switch project scope (All / per-project pill in the input)");
1713
- lines.push("- `Shift+Tab` \u2014 toggle the boss's extended thinking on/off");
1714
- lines.push("- `Esc` \u2014 interrupt the boss while it's running");
1715
- lines.push("- `Ctrl+C` (twice) \u2014 exit");
1716
- lines.push("");
1717
- lines.push("**Inside the Tasks pane (Ctrl+T)**");
1718
- lines.push("- `\u2191` / `\u2193` (or `k` / `j`) \u2014 navigate tasks");
1719
- lines.push("- `r` \u2014 run all pending and blocked tasks across idle workers");
1720
- lines.push("- `d` \u2014 delete the selected task");
1721
- lines.push("- `Esc` \u2014 close the Tasks pane");
1722
- lines.push("");
1723
- lines.push("**Inside model pickers (`/model-boss`, `/model-workers`)**");
1724
- lines.push("- `\u2191` / `\u2193` \u2014 navigate models");
1725
- lines.push("- `Enter` \u2014 select");
1726
- lines.push("- `Esc` \u2014 cancel");
1727
- lines.push("");
1728
- lines.push("**Radio** (`/radio`)");
1729
- lines.push("- Pick a station to stream while you work, or select `Off` to stop.");
1730
- lines.push("- Requires `mpv` (recommended), `ffplay`, `mpg123`, or `vlc/cvlc` installed.");
1731
- lines.push("");
1732
- lines.push("**Input area**");
1733
- lines.push("- `\u2191` / `\u2193` \u2014 recall previous prompts (when input is empty)");
1734
- lines.push("- `Enter` \u2014 send \xB7 `Shift+Enter` \u2014 newline");
1735
- lines.push("- `/` \u2014 open the slash-command menu (Tab / arrows to pick, Enter to insert)");
1736
- return lines.join("\n");
1806
+ function BossModelSelectList({
1807
+ items,
1808
+ onSelect,
1809
+ onCancel,
1810
+ initialIndex
1811
+ }) {
1812
+ const theme = useTheme();
1813
+ const { columns } = useTerminalSize();
1814
+ const [selectedIndex, setSelectedIndex] = (0, import_react9.useState)(initialIndex);
1815
+ const [filter, setFilter] = (0, import_react9.useState)("");
1816
+ const filtered = (0, import_react9.useMemo)(() => {
1817
+ if (!filter) return items;
1818
+ const lower = filter.toLowerCase();
1819
+ return items.filter(
1820
+ (item) => item.label.toLowerCase().includes(lower) || item.value.toLowerCase().includes(lower) || item.description.toLowerCase().includes(lower)
1821
+ );
1822
+ }, [items, filter]);
1823
+ use_input_default((input, key) => {
1824
+ const inputWithoutFocusReports = stripTerminalFocusSequences2(input);
1825
+ if (!inputWithoutFocusReports && input) return;
1826
+ input = inputWithoutFocusReports;
1827
+ if (key.escape) {
1828
+ onCancel();
1829
+ return;
1830
+ }
1831
+ if (key.return) {
1832
+ const selected = filtered[selectedIndex];
1833
+ if (selected) onSelect(selected.value);
1834
+ return;
1835
+ }
1836
+ if (key.upArrow) {
1837
+ setSelectedIndex((i) => Math.max(0, i - 1));
1838
+ return;
1839
+ }
1840
+ if (key.downArrow) {
1841
+ setSelectedIndex((i) => filtered.length === 0 ? 0 : Math.min(filtered.length - 1, i + 1));
1842
+ return;
1843
+ }
1844
+ if (key.backspace || key.delete) {
1845
+ setFilter((current) => current.slice(0, -1));
1846
+ setSelectedIndex(0);
1847
+ return;
1848
+ }
1849
+ if (input && !key.ctrl && !key.meta) {
1850
+ setFilter((current) => current + input);
1851
+ setSelectedIndex(0);
1852
+ }
1853
+ });
1854
+ const total = filtered.length;
1855
+ const idx = Math.min(Math.max(selectedIndex, 0), Math.max(0, total - 1));
1856
+ const start = total <= MAX_MODELS_TO_SHOW ? 0 : Math.max(0, Math.min(idx - Math.floor(MAX_MODELS_TO_SHOW / 2), total - MAX_MODELS_TO_SHOW));
1857
+ const end = Math.min(start + MAX_MODELS_TO_SHOW, total);
1858
+ const visible = filtered.slice(start, end);
1859
+ const width = Math.max(20, columns);
1860
+ const maxLabelLength = Math.max(0, ...filtered.map((item) => item.label.length));
1861
+ const labelColumnWidth = Math.min(maxLabelLength, Math.floor(width * 0.5));
1862
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box_default, { flexDirection: "column", paddingX: 1, width, children: [
1863
+ filter && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { color: theme.textDim, children: [
1864
+ "Filter: ",
1865
+ filter
1866
+ ] }),
1867
+ start > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.text, children: "\u25B2" }),
1868
+ visible.map((item, i) => {
1869
+ const actualIndex = start + i;
1870
+ const isSelected = actualIndex === idx;
1871
+ const textColor = isSelected ? theme.commandColor : theme.textDim;
1872
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1873
+ Box_default,
1874
+ {
1875
+ flexDirection: "row",
1876
+ backgroundColor: isSelected ? theme.border : void 0,
1877
+ children: [
1878
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Box_default, { width: labelColumnWidth, flexShrink: 0, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: textColor, children: item.label }) }),
1879
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Box_default, { flexGrow: 1, paddingLeft: 3, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: textColor, wrap: "truncate", children: item.description.slice(0, 100) }) })
1880
+ ]
1881
+ },
1882
+ item.value
1883
+ );
1884
+ }),
1885
+ end < total && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.textDim, children: "\u25BC" }),
1886
+ total > MAX_MODELS_TO_SHOW && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { color: theme.textDim, children: [
1887
+ "(",
1888
+ idx + 1,
1889
+ "/",
1890
+ total,
1891
+ ")"
1892
+ ] }),
1893
+ total === 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.textDim, children: "No matches" })
1894
+ ] });
1895
+ }
1896
+ function BossModelSelector({
1897
+ onSelect,
1898
+ onCancel,
1899
+ currentModel,
1900
+ currentProvider
1901
+ }) {
1902
+ const currentValue = `${currentProvider}:${currentModel}`;
1903
+ const items = (0, import_react9.useMemo)(
1904
+ () => MODELS.map((model) => {
1905
+ const value = `${model.provider}:${model.id}`;
1906
+ const isCurrent = value === currentValue;
1907
+ return {
1908
+ label: `${isCurrent ? "* " : " "}${model.name}`,
1909
+ value,
1910
+ description: `${PROVIDER_LABEL[model.provider] ?? model.provider} \xB7 ${model.id}`
1911
+ };
1912
+ }),
1913
+ [currentValue]
1914
+ );
1915
+ const initialIndex = Math.max(
1916
+ 0,
1917
+ items.findIndex((item) => item.value === currentValue)
1918
+ );
1919
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1920
+ BossModelSelectList,
1921
+ {
1922
+ items,
1923
+ onSelect,
1924
+ onCancel,
1925
+ initialIndex
1926
+ }
1927
+ );
1737
1928
  }
1738
1929
 
1739
- // src/tool-formatters.ts
1930
+ // src/boss-tasks-overlay.tsx
1740
1931
  init_esm_shims();
1932
+ var import_react10 = __toESM(require_react(), 1);
1741
1933
 
1742
1934
  // src/colors.ts
1743
1935
  init_esm_shims();
@@ -1770,206 +1962,48 @@ function projectColor(name) {
1770
1962
  return PROJECT_COLORS[stableHash(name) % PROJECT_COLORS.length];
1771
1963
  }
1772
1964
 
1773
- // src/tool-formatters.ts
1774
- function truncate2(s, max) {
1775
- if (max <= 1) return "\u2026";
1776
- return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
1777
- }
1778
- function promptWorkerDetailLen(project) {
1779
- const cols = process.stdout.columns ?? 80;
1780
- const fixed = 2 + 13 + 1 + project.length + 3 + 1 + 1 + 11 + 6;
1781
- return Math.max(20, cols - fixed);
1965
+ // src/boss-tasks-overlay.tsx
1966
+ var import_jsx_runtime10 = __toESM(require_jsx_runtime(), 1);
1967
+ function statusGlyph(status) {
1968
+ switch (status) {
1969
+ case "done":
1970
+ return "\u2713";
1971
+ case "in_progress":
1972
+ return "~";
1973
+ case "blocked":
1974
+ return "\u2717";
1975
+ case "skipped":
1976
+ return "\u2014";
1977
+ default:
1978
+ return " ";
1979
+ }
1782
1980
  }
1783
- var bossToolFormatters = {
1784
- formatLabel(name) {
1785
- switch (name) {
1786
- case "list_workers":
1787
- return "List Workers";
1788
- case "get_worker_status":
1789
- return "Worker Status";
1790
- case "prompt_worker":
1791
- return "Prompt Worker";
1792
- case "get_worker_summary":
1793
- return "Worker Summary";
1794
- default:
1795
- return void 0;
1796
- }
1797
- },
1798
- formatDetail(name, args) {
1799
- switch (name) {
1800
- case "list_workers":
1801
- return "";
1802
- case "get_worker_status":
1803
- case "get_worker_summary":
1804
- return truncate2(String(args.project ?? ""), 40);
1805
- case "prompt_worker": {
1806
- const project = String(args.project ?? "");
1807
- const message = String(args.message ?? "").replace(/\s+/g, " ");
1808
- const fresh = args.fresh === true;
1809
- const maxMsg = promptWorkerDetailLen(project) - (fresh ? 8 : 0);
1810
- const truncMsg = truncate2(message, Math.max(15, maxMsg));
1811
- const head = fresh ? "fresh \xB7 " : "";
1812
- return project ? `${head}${project} \xB7 ${truncMsg}` : `${head}${truncMsg}`;
1813
- }
1814
- default:
1815
- return void 0;
1816
- }
1817
- },
1818
- formatInline(name, result, isError) {
1819
- if (isError) return void 0;
1820
- switch (name) {
1821
- case "list_workers": {
1822
- const lines = result.split("\n").filter((l) => l.startsWith("-"));
1823
- return `${lines.length} worker${lines.length === 1 ? "" : "s"}`;
1824
- }
1825
- case "prompt_worker": {
1826
- if (result.includes("currently working")) {
1827
- return { text: "busy \u2014 skipped", color: "#fbbf24" };
1828
- }
1829
- if (result.includes("Unknown project")) {
1830
- return { text: "unknown project", color: "#f87171" };
1831
- }
1832
- const project = String(result.match(/"([^"]+)"/)?.[1] ?? "");
1833
- const color = project ? projectColor(project) : "#e11d48";
1834
- return { text: "dispatched", color };
1835
- }
1836
- case "get_worker_status": {
1837
- const parts = result.split(":");
1838
- if (parts.length < 2) return void 0;
1839
- const status = parts.slice(1).join(":").trim();
1840
- const project = parts[0].trim();
1841
- return { text: status, color: projectColor(project) };
1842
- }
1843
- case "get_worker_summary": {
1844
- const turnMatch = result.match(/Turn:\s*(\d+)/);
1845
- const projectMatch = result.match(/Project:\s*(.+)/);
1846
- const toolsMatch = result.match(/Tools used:\s*(.+)/);
1847
- const tools = toolsMatch ? toolsMatch[1] : "";
1848
- const toolCount = tools && tools !== "(no tools used)" ? tools.split(",").length : 0;
1849
- const turn = turnMatch ? `turn ${turnMatch[1]}` : void 0;
1850
- const tCount = toolCount > 0 ? `${toolCount} tool${toolCount === 1 ? "" : "s"}` : void 0;
1851
- const summary = [turn, tCount].filter(Boolean).join(" \xB7 ");
1852
- if (!summary) return void 0;
1853
- const project = projectMatch ? projectMatch[1].trim() : "";
1854
- return project ? { text: summary, color: projectColor(project) } : { text: summary, color: "#9ca3af" };
1855
- }
1856
- default:
1857
- return void 0;
1858
- }
1859
- }
1860
- };
1861
-
1862
- // src/boss-phrases.ts
1863
- init_esm_shims();
1864
- var BOSS_PHRASES = {
1865
- // Generic between-states fallback. Probably never shown but keep for safety.
1866
- idle: ["Standing by", "Waiting for orders", "On call"],
1867
- // Boss has issued a request, waiting for the LLM to begin streaming.
1868
- waiting: [
1869
- "Briefing",
1870
- "Reviewing the room",
1871
- "Triaging",
1872
- "Lining up the brief",
1873
- "Surveying projects",
1874
- "Reading the room",
1875
- "Picking the right hand",
1876
- "Marshalling thoughts",
1877
- "Checking the board",
1878
- "Sizing up the work"
1879
- ],
1880
- // LLM is mid-thinking-block (extended reasoning).
1881
- thinking: [
1882
- "Strategising",
1883
- "Plotting next move",
1884
- "Weighing options",
1885
- "Reasoning",
1886
- "Deliberating",
1887
- "Thinking it through",
1888
- "Mapping the play",
1889
- "Considering angles",
1890
- "Calculating odds",
1891
- "Drafting the call"
1892
- ],
1893
- // LLM is streaming text — boss is forming its dispatch / response.
1894
- generating: [
1895
- "Drafting",
1896
- "Composing dispatch",
1897
- "Writing the brief",
1898
- "Penning instructions",
1899
- "Wording it up",
1900
- "Putting it on paper",
1901
- "Phrasing the ask",
1902
- "Forming the directive",
1903
- "Scripting the plan"
1904
- ],
1905
- // Boss is invoking a tool — most often prompt_worker.
1906
- tools: [
1907
- "Coordinating",
1908
- "Dispatching",
1909
- "Routing",
1910
- "Delegating",
1911
- "Issuing orders",
1912
- "Handing off",
1913
- "Aligning workers",
1914
- "Conducting",
1915
- "Calling the team",
1916
- "Steering",
1917
- "Pulling levers"
1918
- ],
1919
- // Provider retry (overloaded / rate-limited / etc.).
1920
- retrying: [
1921
- "Reattempting",
1922
- "Course correcting",
1923
- "Trying again",
1924
- "Pushing through",
1925
- "Holding the line"
1926
- ]
1927
- };
1928
-
1929
- // src/boss-tasks-overlay.tsx
1930
- init_esm_shims();
1931
- var import_react8 = __toESM(require_react(), 1);
1932
- var import_jsx_runtime8 = __toESM(require_jsx_runtime(), 1);
1933
- function statusGlyph(status) {
1934
- switch (status) {
1935
- case "done":
1936
- return "\u2713";
1937
- case "in_progress":
1938
- return "~";
1939
- case "blocked":
1940
- return "\u2717";
1941
- case "skipped":
1942
- return "\u2014";
1943
- default:
1944
- return " ";
1945
- }
1946
- }
1947
- function BossTasksOverlay({
1948
- boss,
1949
- workers,
1950
- onClose
1951
- }) {
1952
- const theme = useTheme();
1953
- const tasksState = useTasksState();
1954
- const tasks = tasksState.tasks;
1955
- const [selectedIndex, setSelectedIndex] = (0, import_react8.useState)(0);
1956
- const [status, setStatusMsg] = (0, import_react8.useState)("");
1957
- const statusTimer = (0, import_react8.useRef)(null);
1958
- const showStatus = (0, import_react8.useCallback)((msg) => {
1959
- setStatusMsg(msg);
1960
- if (statusTimer.current) clearTimeout(statusTimer.current);
1961
- statusTimer.current = setTimeout(() => setStatusMsg(""), 2500);
1962
- }, []);
1963
- const groupedTasks = workers.map((w) => ({
1964
- project: w.name,
1965
- tasks: tasks.filter((t) => t.project === w.name).sort((a, b) => a.createdAt.localeCompare(b.createdAt))
1966
- }));
1967
- const flatTasks = groupedTasks.flatMap((g) => g.tasks);
1968
- (0, import_react8.useEffect)(() => {
1969
- if (flatTasks.length === 0) {
1970
- setSelectedIndex(0);
1971
- } else if (selectedIndex >= flatTasks.length) {
1972
- setSelectedIndex(flatTasks.length - 1);
1981
+ function BossTasksOverlay({
1982
+ boss,
1983
+ workers,
1984
+ onClose
1985
+ }) {
1986
+ const theme = useTheme();
1987
+ const tasksState = useTasksState();
1988
+ const tasks = tasksState.tasks;
1989
+ const [selectedIndex, setSelectedIndex] = (0, import_react10.useState)(0);
1990
+ const [status, setStatusMsg] = (0, import_react10.useState)("");
1991
+ const statusTimer = (0, import_react10.useRef)(null);
1992
+ const showStatus = (0, import_react10.useCallback)((msg) => {
1993
+ setStatusMsg(msg);
1994
+ if (statusTimer.current) clearTimeout(statusTimer.current);
1995
+ statusTimer.current = setTimeout(() => setStatusMsg(""), 2500);
1996
+ }, []);
1997
+ const groupedTasks = workers.map((w) => ({
1998
+ project: w.name,
1999
+ tasks: tasks.filter((t) => t.project === w.name).sort((a, b) => a.createdAt.localeCompare(b.createdAt))
2000
+ }));
2001
+ const flatTasks = groupedTasks.flatMap((g) => g.tasks);
2002
+ (0, import_react10.useEffect)(() => {
2003
+ if (flatTasks.length === 0) {
2004
+ setSelectedIndex(0);
2005
+ } else if (selectedIndex >= flatTasks.length) {
2006
+ setSelectedIndex(flatTasks.length - 1);
1973
2007
  }
1974
2008
  }, [flatTasks.length, selectedIndex]);
1975
2009
  const selected = flatTasks[selectedIndex];
@@ -2025,11 +2059,11 @@ function BossTasksOverlay({
2025
2059
  const inProgressCount = tasks.filter((t) => t.status === "in_progress").length;
2026
2060
  const pendingCount = tasks.filter((t) => t.status === "pending").length;
2027
2061
  const blockedCount = tasks.filter((t) => t.status === "blocked").length;
2028
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box_default, { flexDirection: "column", marginTop: 1, paddingX: 1, children: [
2029
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box_default, { children: [
2030
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: COLORS.primary, bold: true, children: "Tasks" }),
2031
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.textDim, children: ` \xB7 ${tasks.length} total \xB7 ` }),
2032
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2062
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "column", marginTop: 1, paddingX: 1, children: [
2063
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { children: [
2064
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: COLORS.primary, bold: true, children: "Tasks" }),
2065
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: theme.textDim, children: ` \xB7 ${tasks.length} total \xB7 ` }),
2066
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2033
2067
  CountsRow,
2034
2068
  {
2035
2069
  theme,
@@ -2040,28 +2074,28 @@ function BossTasksOverlay({
2040
2074
  }
2041
2075
  )
2042
2076
  ] }),
2043
- flatTasks.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { color: theme.textDim, children: [
2077
+ flatTasks.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { color: theme.textDim, children: [
2044
2078
  " No tasks yet. Ask the boss to plan some \u2014 e.g. ",
2045
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.text, children: '"plan some work"' }),
2079
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: theme.text, children: '"plan some work"' }),
2046
2080
  "."
2047
2081
  ] }) }),
2048
- showingTop && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.textDim, children: ` \u2191 ${startIdx} more above` }),
2082
+ showingTop && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: theme.textDim, children: ` \u2191 ${startIdx} more above` }),
2049
2083
  groupedTasks.map((group, gIdx) => {
2050
2084
  const startInFlat = groupedTasks.slice(0, gIdx).reduce((acc, g) => acc + g.tasks.length, 0);
2051
2085
  const visibleInSection = group.tasks.filter((t) => visibleIdSet.has(t.id));
2052
2086
  if (visibleInSection.length === 0) return null;
2053
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box_default, { flexDirection: "column", marginTop: 1, children: [
2054
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { children: [
2055
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: projectColor(group.project), bold: true, children: group.project }),
2056
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.textDim, children: ` \xB7 ${group.tasks.length}` })
2087
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "column", marginTop: 1, children: [
2088
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { children: [
2089
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: projectColor(group.project), bold: true, children: group.project }),
2090
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: theme.textDim, children: ` \xB7 ${group.tasks.length}` })
2057
2091
  ] }),
2058
2092
  visibleInSection.map((task) => {
2059
2093
  const realIdx = startInFlat + group.tasks.indexOf(task);
2060
2094
  const isSelected = realIdx === selectedIndex;
2061
2095
  const prefix = isSelected ? "\u276F " : " ";
2062
2096
  const glyph = statusGlyph(task.status);
2063
- const color = isSelected ? theme.primary : task.status === "done" ? theme.success : task.status === "in_progress" ? theme.warning : task.status === "blocked" ? theme.error : theme.text;
2064
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { color, bold: isSelected, children: [
2097
+ const color2 = isSelected ? theme.primary : task.status === "done" ? theme.success : task.status === "in_progress" ? theme.warning : task.status === "blocked" ? theme.error : theme.text;
2098
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { color: color2, bold: isSelected, children: [
2065
2099
  prefix,
2066
2100
  "[",
2067
2101
  glyph,
@@ -2071,16 +2105,16 @@ function BossTasksOverlay({
2071
2105
  })
2072
2106
  ] }, group.project);
2073
2107
  }),
2074
- showingBottom && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.textDim, children: ` \u2193 ${flatTasks.length - endIdx} more below` }),
2075
- status && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.success, children: " " + status }) }),
2076
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { color: theme.textDim, children: [
2077
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.primary, children: "\u2191\u2193" }),
2108
+ showingBottom && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: theme.textDim, children: ` \u2193 ${flatTasks.length - endIdx} more below` }),
2109
+ status && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: theme.success, children: " " + status }) }),
2110
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { color: theme.textDim, children: [
2111
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: theme.primary, children: "\u2191\u2193" }),
2078
2112
  " move \xB7 (",
2079
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.primary, children: "d" }),
2113
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: theme.primary, children: "d" }),
2080
2114
  ")elete \xB7 (",
2081
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.primary, children: "r" }),
2115
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: theme.primary, children: "r" }),
2082
2116
  ")un pending \xB7 ",
2083
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.primary, children: "ESC" }),
2117
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: theme.primary, children: "ESC" }),
2084
2118
  " close"
2085
2119
  ] }) })
2086
2120
  ] });
@@ -2092,24 +2126,24 @@ function CountsRow({
2092
2126
  pending,
2093
2127
  blocked
2094
2128
  }) {
2095
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { children: [
2096
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { color: theme.success, children: [
2129
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { children: [
2130
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { color: theme.success, children: [
2097
2131
  done,
2098
2132
  " done"
2099
2133
  ] }),
2100
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.textDim, children: " \xB7 " }),
2101
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { color: theme.warning, children: [
2134
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: theme.textDim, children: " \xB7 " }),
2135
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { color: theme.warning, children: [
2102
2136
  active,
2103
2137
  " active"
2104
2138
  ] }),
2105
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.textDim, children: " \xB7 " }),
2106
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { color: theme.text, children: [
2139
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: theme.textDim, children: " \xB7 " }),
2140
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { color: theme.text, children: [
2107
2141
  pending,
2108
2142
  " pending"
2109
2143
  ] }),
2110
- blocked > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
2111
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.textDim, children: " \xB7 " }),
2112
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { color: theme.error, children: [
2144
+ blocked > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
2145
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: theme.textDim, children: " \xB7 " }),
2146
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { color: theme.error, children: [
2113
2147
  blocked,
2114
2148
  " blocked"
2115
2149
  ] })
@@ -2117,9 +2151,100 @@ function CountsRow({
2117
2151
  ] });
2118
2152
  }
2119
2153
 
2154
+ // src/boss-worker-status-row.tsx
2155
+ init_esm_shims();
2156
+ var import_react11 = __toESM(require_react(), 1);
2157
+ var import_jsx_runtime11 = __toESM(require_jsx_runtime(), 1);
2158
+ var SHIMMER_WIDTH = 3;
2159
+ function formatWorkerElapsed(ms) {
2160
+ const total = Math.floor(ms / 1e3);
2161
+ const m = Math.floor(total / 60);
2162
+ const s = total % 60;
2163
+ return `${m}:${s.toString().padStart(2, "0")}`;
2164
+ }
2165
+ function AnimationActiveSentinel() {
2166
+ useAnimationActive();
2167
+ return null;
2168
+ }
2169
+ function ShimmerName({
2170
+ name,
2171
+ color: color2,
2172
+ tick
2173
+ }) {
2174
+ const cycle = name.length + SHIMMER_WIDTH * 2;
2175
+ const shimmerPos = tick % cycle - SHIMMER_WIDTH;
2176
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { children: name.split("").map((ch, i) => {
2177
+ const isBright = Math.abs(i - shimmerPos) <= SHIMMER_WIDTH;
2178
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: color2, bold: isBright, dimColor: !isBright, children: ch }, i);
2179
+ }) });
2180
+ }
2181
+ function BossWorkerStatusRow({
2182
+ workers,
2183
+ pendingMessages
2184
+ }) {
2185
+ const theme = useTheme();
2186
+ const { columns } = useTerminalSize();
2187
+ const working = workers.filter((w) => w.status === "working");
2188
+ const errored = workers.filter((w) => w.status === "error");
2189
+ const idleCount = workers.length - working.length - errored.length;
2190
+ const anyWorking = working.length > 0;
2191
+ const tick = useAnimationTick();
2192
+ const now = Date.now();
2193
+ if (workers.length === 0) return null;
2194
+ const slots = [];
2195
+ for (const w of working) {
2196
+ const projectHue = projectColor(w.name);
2197
+ const elapsed = w.workStartedAt ? formatWorkerElapsed(now - w.workStartedAt) : null;
2198
+ slots.push(
2199
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_react11.default.Fragment, { children: [
2200
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ShimmerName, { name: w.name, color: projectHue, tick }),
2201
+ elapsed && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { color: theme.textDim, children: [
2202
+ " ",
2203
+ elapsed
2204
+ ] })
2205
+ ] }, `w-${w.name}`)
2206
+ );
2207
+ }
2208
+ for (const w of errored) {
2209
+ slots.push(
2210
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react11.default.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { color: theme.error, children: [
2211
+ "\u2717 ",
2212
+ w.name
2213
+ ] }) }, `e-${w.name}`)
2214
+ );
2215
+ }
2216
+ if (idleCount > 0) {
2217
+ slots.push(
2218
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react11.default.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { color: theme.textDim, children: [
2219
+ "\u25CB ",
2220
+ idleCount,
2221
+ " idle"
2222
+ ] }) }, "idle")
2223
+ );
2224
+ }
2225
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { paddingX: 1, width: columns, flexShrink: 1, children: [
2226
+ anyWorking && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(AnimationActiveSentinel, {}),
2227
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { wrap: "truncate", children: [
2228
+ slots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_react11.default.Fragment, { children: [
2229
+ i > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: theme.border, children: " \u2502 " }),
2230
+ slot
2231
+ ] }, i)),
2232
+ pendingMessages > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
2233
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: theme.textDim, children: " " }),
2234
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { color: theme.warning, children: [
2235
+ pendingMessages,
2236
+ " message",
2237
+ pendingMessages === 1 ? "" : "s",
2238
+ " queued"
2239
+ ] })
2240
+ ] })
2241
+ ] })
2242
+ ] });
2243
+ }
2244
+
2120
2245
  // src/radio-picker.tsx
2121
2246
  init_esm_shims();
2122
- var import_react9 = __toESM(require_react(), 1);
2247
+ var import_react12 = __toESM(require_react(), 1);
2123
2248
 
2124
2249
  // src/radio.ts
2125
2250
  init_esm_shims();
@@ -2287,7 +2412,7 @@ function buildInstallHint() {
2287
2412
  }
2288
2413
 
2289
2414
  // src/radio-picker.tsx
2290
- var import_jsx_runtime9 = __toESM(require_jsx_runtime(), 1);
2415
+ var import_jsx_runtime12 = __toESM(require_jsx_runtime(), 1);
2291
2416
  function RadioPicker({
2292
2417
  currentStationId: currentStationId2,
2293
2418
  onSelect,
@@ -2309,7 +2434,7 @@ function RadioPicker({
2309
2434
  0,
2310
2435
  items.findIndex((i) => i.value === (currentStationId2 ?? "off"))
2311
2436
  );
2312
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2437
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2313
2438
  SelectList,
2314
2439
  {
2315
2440
  items,
@@ -2321,42 +2446,905 @@ function RadioPicker({
2321
2446
  );
2322
2447
  }
2323
2448
 
2324
- // src/auto-update.ts
2325
- init_esm_shims();
2326
- import { spawn as spawn2 } from "child_process";
2327
- import fs4 from "fs";
2328
- import path4 from "path";
2329
- import os2 from "os";
2330
- var PACKAGE_NAME = "@prestyj/boss";
2331
- var REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
2332
- var CHECK_INTERVAL_MS = 60 * 60 * 1e3;
2333
- var FETCH_TIMEOUT_MS = 1e4;
2334
- function getStateFilePath() {
2335
- return path4.join(os2.homedir(), ".ezcoder", "boss", "update-state.json");
2336
- }
2337
- function readState() {
2338
- try {
2339
- const raw = fs4.readFileSync(getStateFilePath(), "utf-8");
2340
- return JSON.parse(raw);
2341
- } catch {
2342
- return null;
2343
- }
2344
- }
2345
- function writeState(state) {
2346
- try {
2347
- const dir = path4.dirname(getStateFilePath());
2348
- fs4.mkdirSync(dir, { recursive: true, mode: 448 });
2349
- fs4.writeFileSync(getStateFilePath(), JSON.stringify(state));
2350
- } catch {
2449
+ // src/boss-chat-screen.tsx
2450
+ var import_jsx_runtime13 = __toESM(require_jsx_runtime(), 1);
2451
+ function BossChatScreen({
2452
+ boss,
2453
+ columns,
2454
+ state,
2455
+ overlay,
2456
+ controlsRef = () => {
2457
+ },
2458
+ livePane,
2459
+ theme,
2460
+ statusSlotVisible,
2461
+ activityVisible,
2462
+ stallStatusVisible,
2463
+ doneStatus,
2464
+ elapsedMs,
2465
+ runStartRef,
2466
+ charCountRef,
2467
+ realTokensAccumRef,
2468
+ lastUserMessage,
2469
+ activeToolNames,
2470
+ inputActive,
2471
+ isRunning,
2472
+ onSubmit,
2473
+ onAbort,
2474
+ onTab,
2475
+ onShiftTab,
2476
+ commands,
2477
+ scopeBadge,
2478
+ onCloseOverlay,
2479
+ onModelSelect,
2480
+ currentRadio,
2481
+ onRadioSelect,
2482
+ bossModel,
2483
+ workerModel,
2484
+ updatePending,
2485
+ currentRadioStationId,
2486
+ workers,
2487
+ pendingMessages,
2488
+ formatDuration
2489
+ }) {
2490
+ if (overlay === "tasks") {
2491
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ChatLayout, { columns, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(BossTasksOverlay, { boss, workers, onClose: onCloseOverlay }) });
2351
2492
  }
2493
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(ChatLayout, { columns, children: [
2494
+ livePane,
2495
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(ChatControls, { controlsRef, children: [
2496
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2497
+ ChatInputStack,
2498
+ {
2499
+ columns,
2500
+ theme,
2501
+ statusSlotVisible,
2502
+ activityVisible,
2503
+ stallStatusVisible,
2504
+ liveToolFeed: [],
2505
+ doneStatus,
2506
+ activityPhase: state.activityPhase,
2507
+ elapsedMs,
2508
+ runStartRef,
2509
+ thinkingMs: state.streaming?.thinkingMs ?? 0,
2510
+ isThinking: state.activityPhase === "thinking",
2511
+ thinkingLevel: state.bossThinkingLevel,
2512
+ tokenEstimate: state.bossInputTokens,
2513
+ charCountRef,
2514
+ realTokensAccumRef,
2515
+ lastUserMessage,
2516
+ activeToolNames,
2517
+ retryInfo: state.retryInfo,
2518
+ planDone: 0,
2519
+ planTotal: 0,
2520
+ renderMarkdown: true,
2521
+ formatDuration
2522
+ }
2523
+ ),
2524
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2525
+ InputArea,
2526
+ {
2527
+ onSubmit,
2528
+ onAbort,
2529
+ disabled: isRunning,
2530
+ isActive: inputActive,
2531
+ cwd: process.cwd(),
2532
+ commands,
2533
+ scopeBadge,
2534
+ disableMouseTracking: true,
2535
+ onTab,
2536
+ onShiftTab
2537
+ }
2538
+ ),
2539
+ overlay === "model-boss" || overlay === "model-workers" ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2540
+ BossModelSelector,
2541
+ {
2542
+ onSelect: onModelSelect,
2543
+ onCancel: onCloseOverlay,
2544
+ currentModel: overlay === "model-boss" ? state.bossModel : state.workerModel,
2545
+ currentProvider: overlay === "model-boss" ? state.bossProvider : state.workerProvider
2546
+ }
2547
+ ) : overlay === "radio" ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2548
+ RadioPicker,
2549
+ {
2550
+ currentStationId: currentRadio,
2551
+ onCancel: onCloseOverlay,
2552
+ onSelect: onRadioSelect
2553
+ }
2554
+ ) : /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
2555
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2556
+ BossFooter,
2557
+ {
2558
+ bossModel,
2559
+ workerModel,
2560
+ tokensIn: state.bossInputTokens,
2561
+ exitPending: state.exitPending,
2562
+ bossThinkingLevel: state.bossThinkingLevel,
2563
+ updatePending,
2564
+ currentRadioStationId,
2565
+ scope: state.scope
2566
+ }
2567
+ ),
2568
+ !state.exitPending && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(BossWorkerStatusRow, { workers, pendingMessages })
2569
+ ] })
2570
+ ] })
2571
+ ] });
2352
2572
  }
2353
- function compareVersions(a, b) {
2354
- const pa = a.split(".").map(Number);
2355
- const pb = b.split(".").map(Number);
2356
- for (let i = 0; i < 3; i++) {
2357
- const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
2358
- if (diff !== 0) return diff;
2359
- }
2573
+
2574
+ // src/slash-commands.ts
2575
+ init_esm_shims();
2576
+ var BOSS_SLASH_COMMANDS = [
2577
+ { name: "help", aliases: ["?"], description: "Show available commands" },
2578
+ {
2579
+ name: "model-boss",
2580
+ aliases: ["m", "model", "models"],
2581
+ description: "Switch the orchestrator's model"
2582
+ },
2583
+ { name: "model-workers", aliases: [], description: "Switch every worker's model" },
2584
+ { name: "compact", aliases: [], description: "Compact the boss's context now" },
2585
+ { name: "clear", aliases: [], description: "Clear chat history and terminal" },
2586
+ { name: "radio", aliases: [], description: "Stream a free internet radio station" },
2587
+ { name: "quit", aliases: ["q", "exit"], description: "Exit ezboss" }
2588
+ ];
2589
+ function isSlashCommand(value) {
2590
+ return value.startsWith("/") && !value.startsWith("//");
2591
+ }
2592
+ function parseSlash(value) {
2593
+ if (!isSlashCommand(value)) return null;
2594
+ const rest = value.slice(1).trim();
2595
+ if (!rest) return null;
2596
+ const space = rest.indexOf(" ");
2597
+ if (space === -1) return { name: rest.toLowerCase(), args: "" };
2598
+ return { name: rest.slice(0, space).toLowerCase(), args: rest.slice(space + 1).trim() };
2599
+ }
2600
+ function canonicalName(name) {
2601
+ for (const cmd of BOSS_SLASH_COMMANDS) {
2602
+ if (cmd.name === name) return cmd.name;
2603
+ if (cmd.aliases.includes(name)) return cmd.name;
2604
+ }
2605
+ return null;
2606
+ }
2607
+ function buildHelpText() {
2608
+ const lines = ["**ezboss commands**", ""];
2609
+ for (const cmd of BOSS_SLASH_COMMANDS) {
2610
+ const aliases = cmd.aliases.length > 0 ? ` (${cmd.aliases.map((a) => "/" + a).join(", ")})` : "";
2611
+ lines.push(`- \`/${cmd.name}\`${aliases} \u2014 ${cmd.description}`);
2612
+ }
2613
+ lines.push("");
2614
+ lines.push("**Global keybindings**");
2615
+ lines.push("- `Ctrl+T` \u2014 open the Tasks pane");
2616
+ lines.push("- `Tab` \u2014 switch project scope (All / per-project pill in the input)");
2617
+ lines.push("- `Shift+Tab` \u2014 cycle the boss's thinking level, then off");
2618
+ lines.push("- `Esc` \u2014 interrupt the boss while it's running");
2619
+ lines.push("- `Ctrl+C` (twice) \u2014 exit");
2620
+ lines.push("");
2621
+ lines.push("**Inside the Tasks pane (Ctrl+T)**");
2622
+ lines.push("- `\u2191` / `\u2193` (or `k` / `j`) \u2014 navigate tasks");
2623
+ lines.push("- `r` \u2014 run all pending and blocked tasks across idle workers");
2624
+ lines.push("- `d` \u2014 delete the selected task");
2625
+ lines.push("- `Esc` \u2014 close the Tasks pane");
2626
+ lines.push("");
2627
+ lines.push("**Inside model pickers (`/model`, `/models`, `/model-boss`, `/model-workers`)**");
2628
+ lines.push("- `\u2191` / `\u2193` \u2014 navigate models");
2629
+ lines.push("- `Enter` \u2014 select");
2630
+ lines.push("- `Esc` \u2014 cancel");
2631
+ lines.push("");
2632
+ lines.push("**Radio** (`/radio`)");
2633
+ lines.push("- Pick a station to stream while you work, or select `Off` to stop.");
2634
+ lines.push("- Requires `mpv` (recommended), `ffplay`, `mpg123`, or `vlc/cvlc` installed.");
2635
+ lines.push("");
2636
+ lines.push("**Input area**");
2637
+ lines.push("- `\u2191` / `\u2193` \u2014 recall previous prompts (when input is empty)");
2638
+ lines.push("- `Enter` \u2014 send \xB7 `Shift+Enter` \u2014 newline");
2639
+ lines.push("- `/` \u2014 open the slash-command menu (Tab / arrows to pick, Enter to insert)");
2640
+ return lines.join("\n");
2641
+ }
2642
+
2643
+ // src/boss-transcript-rows.tsx
2644
+ init_esm_shims();
2645
+ var import_react14 = __toESM(require_react(), 1);
2646
+
2647
+ // src/boss-spacing.ts
2648
+ init_esm_shims();
2649
+ var BOSS_SPACING_KINDS = /* @__PURE__ */ new Set([
2650
+ "user",
2651
+ "assistant",
2652
+ "tool_start",
2653
+ "tool_done",
2654
+ "tool_group",
2655
+ "worker_event",
2656
+ "worker_error",
2657
+ "task_dispatch",
2658
+ "info",
2659
+ "update_notice",
2660
+ "compacting",
2661
+ "compacted",
2662
+ "stopped"
2663
+ ]);
2664
+ var BOSS_COMPACT_BOUNDARIES = /* @__PURE__ */ new Set([
2665
+ "user\u2192assistant",
2666
+ "assistant\u2192user",
2667
+ "user\u2192queued"
2668
+ ]);
2669
+
2670
+ // src/tool-formatters.ts
2671
+ init_esm_shims();
2672
+ function truncate2(s, max) {
2673
+ if (max <= 1) return "\u2026";
2674
+ return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
2675
+ }
2676
+ function promptWorkerDetailLen(project) {
2677
+ const cols = process.stdout.columns ?? 80;
2678
+ const fixed = 2 + 13 + 1 + project.length + 3 + 1 + 1 + 11 + 6;
2679
+ return Math.max(20, cols - fixed);
2680
+ }
2681
+ var bossToolFormatters = {
2682
+ formatLabel(name) {
2683
+ switch (name) {
2684
+ case "list_workers":
2685
+ return "List Workers";
2686
+ case "get_worker_status":
2687
+ return "Worker Status";
2688
+ case "prompt_worker":
2689
+ return "Prompt Worker";
2690
+ case "get_worker_summary":
2691
+ return "Worker Summary";
2692
+ default:
2693
+ return void 0;
2694
+ }
2695
+ },
2696
+ formatDetail(name, args) {
2697
+ switch (name) {
2698
+ case "list_workers":
2699
+ return "";
2700
+ case "get_worker_status":
2701
+ case "get_worker_summary":
2702
+ return truncate2(String(args.project ?? ""), 40);
2703
+ case "prompt_worker": {
2704
+ const project = String(args.project ?? "");
2705
+ const message = String(args.message ?? "").replace(/\s+/g, " ");
2706
+ const fresh = args.fresh === true;
2707
+ const maxMsg = promptWorkerDetailLen(project) - (fresh ? 8 : 0);
2708
+ const truncMsg = truncate2(message, Math.max(15, maxMsg));
2709
+ const head = fresh ? "fresh \xB7 " : "";
2710
+ return project ? `${head}${project} \xB7 ${truncMsg}` : `${head}${truncMsg}`;
2711
+ }
2712
+ default:
2713
+ return void 0;
2714
+ }
2715
+ },
2716
+ formatInline(name, result, isError) {
2717
+ if (isError) return void 0;
2718
+ switch (name) {
2719
+ case "list_workers": {
2720
+ const lines = result.split("\n").filter((l) => l.startsWith("-"));
2721
+ return `${lines.length} worker${lines.length === 1 ? "" : "s"}`;
2722
+ }
2723
+ case "prompt_worker": {
2724
+ if (result.includes("currently working")) {
2725
+ return { text: "busy \u2014 skipped", color: "#fbbf24" };
2726
+ }
2727
+ if (result.includes("Unknown project")) {
2728
+ return { text: "unknown project", color: "#f87171" };
2729
+ }
2730
+ const project = String(result.match(/"([^"]+)"/)?.[1] ?? "");
2731
+ const color2 = project ? projectColor(project) : "#e11d48";
2732
+ return { text: "dispatched", color: color2 };
2733
+ }
2734
+ case "get_worker_status": {
2735
+ const parts = result.split(":");
2736
+ if (parts.length < 2) return void 0;
2737
+ const status = parts.slice(1).join(":").trim();
2738
+ const project = parts[0].trim();
2739
+ return { text: status, color: projectColor(project) };
2740
+ }
2741
+ case "get_worker_summary": {
2742
+ const turnMatch = result.match(/Turn:\s*(\d+)/);
2743
+ const projectMatch = result.match(/Project:\s*(.+)/);
2744
+ const toolsMatch = result.match(/Tools used:\s*(.+)/);
2745
+ const tools = toolsMatch ? toolsMatch[1] : "";
2746
+ const toolCount = tools && tools !== "(no tools used)" ? tools.split(",").length : 0;
2747
+ const turn = turnMatch ? `turn ${turnMatch[1]}` : void 0;
2748
+ const tCount = toolCount > 0 ? `${toolCount} tool${toolCount === 1 ? "" : "s"}` : void 0;
2749
+ const summary = [turn, tCount].filter(Boolean).join(" \xB7 ");
2750
+ if (!summary) return void 0;
2751
+ const project = projectMatch ? projectMatch[1].trim() : "";
2752
+ return project ? { text: summary, color: projectColor(project) } : { text: summary, color: "#9ca3af" };
2753
+ }
2754
+ default:
2755
+ return void 0;
2756
+ }
2757
+ }
2758
+ };
2759
+
2760
+ // src/boss-transcript-rows.tsx
2761
+ var import_jsx_runtime14 = __toESM(require_jsx_runtime(), 1);
2762
+ function getBossTranscriptMarginTop({
2763
+ row,
2764
+ previousRow
2765
+ }) {
2766
+ if (row.kind === "banner" || previousRow?.kind === "banner") return 0;
2767
+ if (!BOSS_SPACING_KINDS.has(row.kind)) return 0;
2768
+ return getTranscriptItemMarginTop({
2769
+ item: row,
2770
+ previousLiveItem: previousRow,
2771
+ spacingKinds: BOSS_SPACING_KINDS,
2772
+ compactBoundaries: BOSS_COMPACT_BOUNDARIES
2773
+ });
2774
+ }
2775
+ function BossTranscriptRow({
2776
+ row,
2777
+ previousRow
2778
+ }) {
2779
+ if (row.kind === "banner") {
2780
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Box_default, { paddingX: 1, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(BossBanner, { subtitle: "Orchestrator", showShortcuts: true }) });
2781
+ }
2782
+ const marginTop = getBossTranscriptMarginTop({ row, previousRow });
2783
+ const renderWithSpacing = (node) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(TranscriptItemFrame, { marginTop, children: node });
2784
+ if (row.kind === "user") return renderWithSpacing(/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(UserMessage, { text: row.text }));
2785
+ if (row.kind === "assistant") return renderWithSpacing(/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(AssistantRow, { item: row }));
2786
+ if (row.kind === "tool_start") return renderWithSpacing(/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ToolStartHistoryRow, { item: row }));
2787
+ if (row.kind === "tool_done") return renderWithSpacing(/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ToolHistoryRow, { item: row }));
2788
+ if (row.kind === "tool_group") return renderWithSpacing(/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ToolGroupRow, { item: row }));
2789
+ if (row.kind === "worker_event") return renderWithSpacing(/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(WorkerEventRow, { item: row }));
2790
+ if (row.kind === "worker_error") return renderWithSpacing(/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(WorkerErrorRow, { item: row }));
2791
+ if (row.kind === "info") {
2792
+ return renderWithSpacing(/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(InfoRow, { text: row.text, level: row.level ?? "info" }));
2793
+ }
2794
+ if (row.kind === "task_dispatch") return renderWithSpacing(/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(TaskDispatchRow, { tasks: row.tasks }));
2795
+ if (row.kind === "update_notice") return renderWithSpacing(/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(UpdateNoticeRow, { text: row.text }));
2796
+ if (row.kind === "compacting") return renderWithSpacing(/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(CompactionSpinner, { staticDisplay: true }));
2797
+ if (row.kind === "compacted") {
2798
+ return renderWithSpacing(
2799
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2800
+ CompactionDone,
2801
+ {
2802
+ originalCount: row.originalCount,
2803
+ newCount: row.newCount,
2804
+ tokensBefore: row.tokensBefore,
2805
+ tokensAfter: row.tokensAfter
2806
+ }
2807
+ )
2808
+ );
2809
+ }
2810
+ if (row.kind === "stopped") return renderWithSpacing(/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(InfoRow, { text: row.text, level: "warning" }));
2811
+ return null;
2812
+ }
2813
+ function UpdateNoticeRow({ text }) {
2814
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Box_default, { flexShrink: 1, borderStyle: "round", borderColor: COLORS.accent, paddingX: 1, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(Text, { wrap: "wrap", children: [
2815
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { color: COLORS.accent, bold: true, children: "\u2728 " }),
2816
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { color: COLORS.primary, bold: true, children: text })
2817
+ ] }) });
2818
+ }
2819
+ function TaskDispatchRow({
2820
+ tasks
2821
+ }) {
2822
+ const theme = useTheme();
2823
+ const count = tasks.length;
2824
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(Box_default, { flexDirection: "column", paddingX: 1, children: [
2825
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(Text, { children: [
2826
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { color: COLORS.primary, bold: true, children: "\u23FA " }),
2827
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(Text, { color: theme.text, bold: true, children: [
2828
+ "Running ",
2829
+ count,
2830
+ " task",
2831
+ count === 1 ? "" : "s",
2832
+ ":"
2833
+ ] })
2834
+ ] }),
2835
+ tasks.map((t, i) => /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(Text, { children: [
2836
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { color: theme.textDim, children: " \u2022 " }),
2837
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { color: projectColor(t.project), bold: true, children: t.project }),
2838
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { color: theme.textDim, children: ": " }),
2839
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { color: theme.text, children: t.title })
2840
+ ] }, `${t.project}-${i}`))
2841
+ ] });
2842
+ }
2843
+ var SHORTCUT_PATTERNS = [
2844
+ // Modifier+Key combos: Ctrl+T, Shift+Tab, Cmd+K, Ctrl+Shift+P, Ctrl+C
2845
+ /\b(?:Ctrl|Cmd|Alt|Option|Opt|Shift|Meta|Win|Super)(?:\s*\+\s*(?:Ctrl|Cmd|Alt|Option|Opt|Shift|Meta|Win|Super))*\s*\+\s*(?:Tab|Enter|Esc|Escape|Space|Backspace|Delete|Del|Home|End|PageUp|PageDown|Up|Down|Left|Right|F[1-9]|F1[0-2]|[A-Z0-9]|\/|\?|\.|,|;|=|-)\b/g,
2846
+ // Bare named keys (only when surrounded by clear key context)
2847
+ /\b(?:Ctrl-[A-Z]|F[1-9]|F1[0-2])\b/g
2848
+ ];
2849
+ function highlightShortcuts(text) {
2850
+ if (!text) return text;
2851
+ const SENTINEL = "\uE000";
2852
+ const masks = [];
2853
+ let masked = text.replace(/```[\s\S]*?```|`[^`]+`/g, (m) => {
2854
+ const idx = masks.push(m) - 1;
2855
+ return `${SENTINEL}${idx}${SENTINEL}`;
2856
+ });
2857
+ for (const re of SHORTCUT_PATTERNS) {
2858
+ masked = masked.replace(re, (m) => `\`${m}\``);
2859
+ }
2860
+ return masked.replace(
2861
+ new RegExp(`${SENTINEL}(\\d+)${SENTINEL}`, "g"),
2862
+ (_, i) => masks[Number(i)]
2863
+ );
2864
+ }
2865
+ function AssistantRow({ item }) {
2866
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(AssistantMessage, { text: highlightShortcuts(item.text), renderMarkdown: true });
2867
+ }
2868
+ function ToolStartHistoryRow({
2869
+ item
2870
+ }) {
2871
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2872
+ ToolExecution,
2873
+ {
2874
+ status: "running",
2875
+ name: item.name,
2876
+ args: item.args,
2877
+ formatters: bossToolFormatters
2878
+ }
2879
+ );
2880
+ }
2881
+ function ToolGroupRow({ item }) {
2882
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ToolGroupExecution, { tools: item.tools, summaryRenderers: bossToolGroupRenderers });
2883
+ }
2884
+ function ToolHistoryRow({ item }) {
2885
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2886
+ ToolExecution,
2887
+ {
2888
+ status: "done",
2889
+ name: item.name,
2890
+ args: item.args,
2891
+ result: item.result,
2892
+ isError: item.isError,
2893
+ details: item.details,
2894
+ formatters: bossToolFormatters
2895
+ }
2896
+ );
2897
+ }
2898
+ function parseStatusGrade(text) {
2899
+ const matches = [
2900
+ ...text.matchAll(/(?:^|\b)Status:\s*(DONE|UNVERIFIED|PARTIAL|BLOCKED|INFO)\b/gim)
2901
+ ];
2902
+ const last = matches[matches.length - 1];
2903
+ if (!last) return null;
2904
+ return last[1].toUpperCase();
2905
+ }
2906
+ function statusGradeColor(grade, theme) {
2907
+ switch (grade) {
2908
+ case "DONE":
2909
+ return theme.success;
2910
+ case "UNVERIFIED":
2911
+ case "PARTIAL":
2912
+ return theme.warning;
2913
+ case "BLOCKED":
2914
+ return theme.error;
2915
+ case "INFO":
2916
+ return theme.textDim;
2917
+ default:
2918
+ return theme.textDim;
2919
+ }
2920
+ }
2921
+ function WorkerEventRow({ item }) {
2922
+ const theme = useTheme();
2923
+ const failedCount = item.toolsUsed.filter((t) => !t.ok).length;
2924
+ const grade = parseStatusGrade(item.finalText);
2925
+ const loaderStatus = grade === "BLOCKED" || failedCount > 0 ? "error" : grade === "UNVERIFIED" || grade === "PARTIAL" ? "queued" : "done";
2926
+ const headerColor = loaderStatus === "error" ? theme.toolError : projectColor(item.project);
2927
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(Box_default, { flexDirection: "row", children: [
2928
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ToolUseLoader, { status: loaderStatus }),
2929
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Box_default, { flexGrow: 1, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(Text, { wrap: "wrap", children: [
2930
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { color: headerColor, bold: true, children: item.project }),
2931
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { color: theme.text, children: ` turn ${item.turnIndex}` }),
2932
+ grade && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
2933
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { color: theme.textDim, children: " \xB7 " }),
2934
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { color: statusGradeColor(grade, theme), bold: true, children: grade })
2935
+ ] })
2936
+ ] }) })
2937
+ ] });
2938
+ }
2939
+ function WorkerErrorRow({ item }) {
2940
+ const theme = useTheme();
2941
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(Box_default, { flexDirection: "column", children: [
2942
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(Box_default, { flexDirection: "row", children: [
2943
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ToolUseLoader, { status: "error" }),
2944
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Box_default, { flexGrow: 1, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(Text, { wrap: "wrap", children: [
2945
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { color: theme.toolError, bold: true, children: item.project }),
2946
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { color: theme.textDim, children: " worker error" })
2947
+ ] }) })
2948
+ ] }),
2949
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(MessageResponse, { children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { color: theme.error, wrap: "wrap", children: item.message }) })
2950
+ ] });
2951
+ }
2952
+ function InfoRow({
2953
+ text,
2954
+ level
2955
+ }) {
2956
+ if (level === "info") return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(AssistantMessage, { text });
2957
+ const theme = useTheme();
2958
+ const color2 = level === "error" ? theme.error : theme.warning;
2959
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(Box_default, { flexDirection: "row", children: [
2960
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ToolUseLoader, { status: level === "error" ? "error" : "queued" }),
2961
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Box_default, { flexGrow: 1, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { color: color2, wrap: "wrap", children: text }) })
2962
+ ] });
2963
+ }
2964
+ function BossStreamingTurnView({
2965
+ turn,
2966
+ isRunning,
2967
+ liveItems = [],
2968
+ lastPendingHistoryItem,
2969
+ lastHistoryItem,
2970
+ availableTerminalHeight = 20
2971
+ }) {
2972
+ const visibleLiveItems = liveItems.filter(
2973
+ (item) => item.kind === "user" || item.kind === "tool_start" || item.kind === "tool_done" || item.kind === "tool_group" || item.kind === "compacting" || item.kind === "compacted"
2974
+ );
2975
+ const lastLiveItem = visibleLiveItems[visibleLiveItems.length - 1];
2976
+ const visibleStreamingText = turn?.text ?? "";
2977
+ const previousTranscriptItem = lastPendingHistoryItem ?? lastHistoryItem;
2978
+ const isAwaitingAssistantAfterUser = isRunning && visibleStreamingText.trim().length === 0 && (lastLiveItem?.kind === "user" || !lastLiveItem && previousTranscriptItem?.kind === "user");
2979
+ const shouldReserveStreamingSpacing = isRunning && (visibleStreamingText.trim().length > 0 || visibleLiveItems.some((item) => BOSS_SPACING_KINDS.has(item.kind)) || isAwaitingAssistantAfterUser);
2980
+ const assistantMarginTop = shouldTopSpaceStreamingAssistant({
2981
+ visibleStreamingText,
2982
+ lastLiveItem,
2983
+ lastPendingHistoryItem,
2984
+ lastHistoryItem,
2985
+ spacingKinds: BOSS_SPACING_KINDS,
2986
+ compactBoundaries: BOSS_COMPACT_BOUNDARIES
2987
+ }) ? 1 : 0;
2988
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2989
+ ChatLivePane,
2990
+ {
2991
+ liveItems: visibleLiveItems,
2992
+ renderItem: (_item, index) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2993
+ BossTranscriptRow,
2994
+ {
2995
+ row: visibleLiveItems[index],
2996
+ previousRow: index > 0 ? visibleLiveItems[index - 1] : previousTranscriptItem
2997
+ }
2998
+ ),
2999
+ isRunning,
3000
+ visibleStreamingText,
3001
+ streamingThinking: "",
3002
+ thinkingMs: turn?.thinkingMs ?? 0,
3003
+ reserveStreamingSpacing: shouldReserveStreamingSpacing,
3004
+ renderMarkdown: true,
3005
+ measuredLiveAreaRows: availableTerminalHeight,
3006
+ assistantMarginTop,
3007
+ streamingContinuation: false
3008
+ }
3009
+ );
3010
+ }
3011
+
3012
+ // src/boss-terminal-history.tsx
3013
+ init_esm_shims();
3014
+ function createBossTerminalHistoryPrinter({
3015
+ stream = process.stdout
3016
+ } = {}) {
3017
+ const printed = /* @__PURE__ */ new Set();
3018
+ let previousPrintedKind = null;
3019
+ return {
3020
+ print(items, context, options) {
3021
+ const writeOutput = options?.write ?? ((data) => void stream.write(data));
3022
+ for (const item of items) {
3023
+ if (!options?.force && printed.has(item.id)) continue;
3024
+ const output = serializeBossItemToTerminalHistory(item, context);
3025
+ const formatted = formatHistoryWrite(output, {
3026
+ leadingSeparator: item.kind === "banner" ? false : shouldSeparateTranscriptItemKinds({
3027
+ previousKind: previousPrintedKind ?? void 0,
3028
+ currentKind: item.kind,
3029
+ spacingKinds: BOSS_SPACING_KINDS,
3030
+ compactBoundaries: BOSS_COMPACT_BOUNDARIES
3031
+ }),
3032
+ trailingBlankLine: item.kind === "banner",
3033
+ trailingNewlines: item.kind === "user" ? 1 : void 0
3034
+ });
3035
+ if (formatted.length === 0) continue;
3036
+ printed.add(item.id);
3037
+ writeOutput(formatted);
3038
+ previousPrintedKind = item.kind;
3039
+ }
3040
+ },
3041
+ clear() {
3042
+ printed.clear();
3043
+ previousPrintedKind = null;
3044
+ },
3045
+ resetPrinted() {
3046
+ printed.clear();
3047
+ previousPrintedKind = null;
3048
+ },
3049
+ get printedIds() {
3050
+ return printed;
3051
+ }
3052
+ };
3053
+ }
3054
+ function serializeBossItemToTerminalHistory(item, context) {
3055
+ switch (item.kind) {
3056
+ case "banner":
3057
+ return renderBanner(context);
3058
+ case "worker_event":
3059
+ return renderWorkerEvent(item, context);
3060
+ case "worker_error":
3061
+ return renderWorkerError(item, context);
3062
+ case "task_dispatch":
3063
+ return renderTaskDispatch(item, context);
3064
+ case "tool_group":
3065
+ return renderToolGroup(item, context);
3066
+ case "update_notice":
3067
+ return renderUpdateNotice(item, context);
3068
+ default:
3069
+ return serializeCompletedItemToTerminalHistory(toGGCoderCompletedItem(item), context);
3070
+ }
3071
+ }
3072
+ function renderBanner(context) {
3073
+ const logo = LOGO_LINES.map((lineText) => gradientLine(lineText, GRADIENT));
3074
+ const shortcuts = `${color(COLORS.primary, "^T")} ${dim(context, "tasks ")}${color(
3075
+ COLORS.primary,
3076
+ "Tab"
3077
+ )} ${dim(context, "scope ")}${color(COLORS.primary, "\u21E7Tab")} ${dim(
3078
+ context,
3079
+ "thinking "
3080
+ )}${color(COLORS.primary, "ESC")} ${dim(context, "interrupt")}`;
3081
+ return [
3082
+ "",
3083
+ `${logo[0]}${LOGO_GAP}${color(COLORS.primary, BRAND, true)}${dim(
3084
+ context,
3085
+ ` v${VERSION} \xB7 By `
3086
+ )}${color(COLORS.text, AUTHOR, true)}`,
3087
+ `${logo[1]}${LOGO_GAP}${color(COLORS.accent, "Orchestrator")}`,
3088
+ `${logo[2]}${LOGO_GAP}${shortcuts}`,
3089
+ ""
3090
+ ].join("\n");
3091
+ }
3092
+ function renderUpdateNotice(item, context) {
3093
+ return renderRoundNoticeBox(
3094
+ [`${color(COLORS.accent, "\u2728 ", true)}${color(COLORS.primary, item.text, true)}`],
3095
+ context,
3096
+ COLORS.accent
3097
+ );
3098
+ }
3099
+ function renderToolGroup(item, context) {
3100
+ const tools = item.tools;
3101
+ const allDone = tools.every((tool) => tool.status === "done");
3102
+ const hasError = tools.some((tool) => tool.isError);
3103
+ const status = allDone ? hasError ? "error" : "done" : "running";
3104
+ const label = buildToolGroupSummary(tools, allDone, bossToolGroupRenderers).map((seg) => {
3105
+ const hex = seg.tone ? toolTonePalette(context.theme, seg.tone).primary : context.theme.toolName;
3106
+ return color(hex, seg.text, seg.bold);
3107
+ }).join("");
3108
+ return toolStatusHeader({ status, label, context, labelAlreadyStyled: true });
3109
+ }
3110
+ function renderWorkerEvent(item, context) {
3111
+ const theme = context.theme;
3112
+ const failedCount = item.toolsUsed.filter((tool) => !tool.ok).length;
3113
+ const grade = parseStatusGrade(item.finalText);
3114
+ const isError = grade === "BLOCKED" || failedCount > 0;
3115
+ const isWarning = grade === "UNVERIFIED" || grade === "PARTIAL";
3116
+ const statusColor = isError ? theme.error : isWarning ? theme.warning : theme.success;
3117
+ const headerColor = isError ? theme.error : projectColor(item.project);
3118
+ return toolStatusHeader({
3119
+ status: isError ? "error" : isWarning ? "queued" : "done",
3120
+ label: color(headerColor, item.project, true),
3121
+ suffix: `${color(theme.text, ` turn ${item.turnIndex}`)}${grade ? `${dim(context, " \xB7 ")}${color(statusColor, grade, true)}` : ""}`,
3122
+ context,
3123
+ labelAlreadyStyled: true
3124
+ });
3125
+ }
3126
+ function renderWorkerError(item, context) {
3127
+ return [
3128
+ toolStatusHeader({
3129
+ status: "error",
3130
+ label: color(context.theme.error, item.project, true),
3131
+ suffix: dim(context, " worker error"),
3132
+ context,
3133
+ labelAlreadyStyled: true
3134
+ }),
3135
+ messageResponseText(color(context.theme.error, item.message), context)
3136
+ ].join("\n");
3137
+ }
3138
+ function renderTaskDispatch(item, context) {
3139
+ const count = item.tasks.length;
3140
+ const lines = [
3141
+ toolStatusHeader({
3142
+ status: "done",
3143
+ label: color(context.theme.text, `Running ${count} task${count === 1 ? "" : "s"}:`, true),
3144
+ context,
3145
+ dotColor: COLORS.primary,
3146
+ labelAlreadyStyled: true
3147
+ })
3148
+ ];
3149
+ for (const task of item.tasks) {
3150
+ lines.push(
3151
+ ` \u2022 ${color(projectColor(task.project), task.project, true)}${dim(context, ": ")}${color(context.theme.text, task.title)}`
3152
+ );
3153
+ }
3154
+ return lines.join("\n");
3155
+ }
3156
+ function toGGCoderCompletedItem(item) {
3157
+ switch (item.kind) {
3158
+ case "user":
3159
+ return { kind: "user", id: item.id, text: item.text };
3160
+ case "assistant":
3161
+ return {
3162
+ kind: "assistant",
3163
+ id: item.id,
3164
+ text: item.text,
3165
+ thinking: item.thinking,
3166
+ thinkingMs: item.thinkingMs,
3167
+ continuation: item.continuation
3168
+ };
3169
+ case "tool_start":
3170
+ return {
3171
+ kind: "tool_start",
3172
+ id: item.id,
3173
+ toolCallId: item.toolCallId,
3174
+ name: item.name,
3175
+ args: formatBossToolArgsForHistory(item.name, item.args),
3176
+ startedAt: item.startedAt,
3177
+ animateUntil: item.animateUntil,
3178
+ progressOutput: item.progressOutput
3179
+ };
3180
+ case "tool_done":
3181
+ return {
3182
+ kind: "tool_done",
3183
+ id: item.id,
3184
+ name: item.name,
3185
+ args: formatBossToolArgsForHistory(item.name, item.args),
3186
+ result: formatBossToolResultForHistory(item.name, item.result, item.isError),
3187
+ isError: item.isError,
3188
+ durationMs: item.durationMs,
3189
+ details: item.details
3190
+ };
3191
+ case "info":
3192
+ return { kind: "info", id: item.id, text: item.text };
3193
+ case "compacting":
3194
+ return { kind: "compacting", id: item.id };
3195
+ case "compacted":
3196
+ return {
3197
+ kind: "compacted",
3198
+ id: item.id,
3199
+ originalCount: item.originalCount,
3200
+ newCount: item.newCount,
3201
+ tokensBefore: item.tokensBefore,
3202
+ tokensAfter: item.tokensAfter
3203
+ };
3204
+ case "stopped":
3205
+ return { kind: "stopped", id: item.id, text: item.text };
3206
+ }
3207
+ }
3208
+ function formatBossToolArgsForHistory(name, args) {
3209
+ switch (name) {
3210
+ case "list_workers":
3211
+ return { action: "workers" };
3212
+ case "get_worker_status":
3213
+ case "get_worker_summary":
3214
+ return { action: String(args.project ?? "") };
3215
+ case "prompt_worker": {
3216
+ const project = String(args.project ?? "");
3217
+ const message = String(args.message ?? "").replace(/\s+/gu, " ");
3218
+ const fresh = args.fresh === true ? "fresh \xB7 " : "";
3219
+ const detail = project ? `${fresh}${project} \xB7 ${message}` : `${fresh}${message}`;
3220
+ return {
3221
+ action: truncatePlain(detail, Math.max(20, contextlessPromptWorkerDetailLen(project)))
3222
+ };
3223
+ }
3224
+ default:
3225
+ return args;
3226
+ }
3227
+ }
3228
+ function formatBossToolResultForHistory(name, result, isError) {
3229
+ if (isError) return result;
3230
+ const inline = formatBossToolInlineForHistory(name, result);
3231
+ if (!inline) return result;
3232
+ return typeof inline === "string" ? inline : inline.text;
3233
+ }
3234
+ function formatBossToolInlineForHistory(name, result) {
3235
+ switch (name) {
3236
+ case "list_workers": {
3237
+ const lines = result.split("\n").filter((lineText) => lineText.startsWith("-"));
3238
+ return `${lines.length} worker${lines.length === 1 ? "" : "s"}`;
3239
+ }
3240
+ case "prompt_worker": {
3241
+ if (result.includes("currently working")) return "busy \u2014 skipped";
3242
+ if (result.includes("Unknown project")) return "unknown project";
3243
+ return "dispatched";
3244
+ }
3245
+ case "get_worker_status": {
3246
+ const parts = result.split(":");
3247
+ if (parts.length < 2) return void 0;
3248
+ return parts.slice(1).join(":").trim();
3249
+ }
3250
+ case "get_worker_summary": {
3251
+ const turnMatch = result.match(/Turn:\s*(\d+)/u);
3252
+ const toolsMatch = result.match(/Tools used:\s*(.+)/u);
3253
+ const tools = toolsMatch ? toolsMatch[1] : "";
3254
+ const toolCount = tools && tools !== "(no tools used)" ? tools.split(",").length : 0;
3255
+ const turn = turnMatch ? `turn ${turnMatch[1]}` : void 0;
3256
+ const toolSummary = toolCount > 0 ? `${toolCount} tool${toolCount === 1 ? "" : "s"}` : void 0;
3257
+ return [turn, toolSummary].filter(Boolean).join(" \xB7 ") || void 0;
3258
+ }
3259
+ default:
3260
+ return void 0;
3261
+ }
3262
+ }
3263
+ function contextlessPromptWorkerDetailLen(project) {
3264
+ const cols = process.stdout.columns ?? 80;
3265
+ const fixed = 2 + 13 + 1 + project.length + 3 + 1 + 1 + 11 + 6;
3266
+ return Math.max(20, cols - fixed);
3267
+ }
3268
+ function toolStatusHeader({
3269
+ status,
3270
+ label,
3271
+ suffix = "",
3272
+ context,
3273
+ dotColor,
3274
+ labelAlreadyStyled = false
3275
+ }) {
3276
+ const resolvedDotColor = dotColor ?? (status === "error" ? context.theme.error : status === "done" ? context.theme.success : status === "queued" ? context.theme.warning : context.theme.spinnerColor);
3277
+ const indicator = status === "running" ? "\u280B" : "\u23FA";
3278
+ const labelText = labelAlreadyStyled ? label : color(context.theme.toolName, label, true);
3279
+ return `${RESPONSE_LEFT_PADDING}${color(resolvedDotColor, indicator)} ${labelText}${suffix}`;
3280
+ }
3281
+ function messageResponseText(text, context) {
3282
+ const [first, ...rest] = wrapPlain(text, Math.max(10, context.columns - 8)).split("\n");
3283
+ return [
3284
+ `${RESPONSE_LEFT_PADDING}${dim(context, " \u23BF ")}${first ?? ""}`,
3285
+ ...rest.map((lineText) => `${RESPONSE_LEFT_PADDING}${dim(context, " ")}${lineText}`)
3286
+ ].join("\n");
3287
+ }
3288
+ function renderRoundNoticeBox(lines, context, borderColor) {
3289
+ const width = Math.max(
3290
+ 4,
3291
+ Math.min(
3292
+ context.columns - RESPONSE_LEFT_PADDING.length,
3293
+ Math.max(...lines.map((lineText) => stripAnsi(lineText).length)) + 4
3294
+ )
3295
+ );
3296
+ const contentWidth = Math.max(1, width - 4);
3297
+ const top = `${color(borderColor, "\u256D")}${color(borderColor, "\u2500".repeat(width - 2))}${color(
3298
+ borderColor,
3299
+ "\u256E"
3300
+ )}`;
3301
+ const bottom = `${color(borderColor, "\u2570")}${color(borderColor, "\u2500".repeat(width - 2))}${color(
3302
+ borderColor,
3303
+ "\u256F"
3304
+ )}`;
3305
+ const body = lines.flatMap((lineText) => wrapPlain(lineText, contentWidth).split("\n")).map((lineText) => {
3306
+ const plainLength = stripAnsi(lineText).length;
3307
+ return `${color(borderColor, "\u2502")} ${lineText}${" ".repeat(Math.max(0, contentWidth - plainLength))} ${color(borderColor, "\u2502")}`;
3308
+ });
3309
+ return indent([top, ...body, bottom].join("\n"), RESPONSE_LEFT_PADDING);
3310
+ }
3311
+
3312
+ // src/auto-update.ts
3313
+ init_esm_shims();
3314
+ import { spawn as spawn2 } from "child_process";
3315
+ import fs4 from "fs";
3316
+ import path4 from "path";
3317
+ import os2 from "os";
3318
+ var PACKAGE_NAME = "@prestyj/boss";
3319
+ var REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
3320
+ var CHECK_INTERVAL_MS = 60 * 60 * 1e3;
3321
+ var FETCH_TIMEOUT_MS = 1e4;
3322
+ function getStateFilePath() {
3323
+ return path4.join(os2.homedir(), ".ezcoder", "boss", "update-state.json");
3324
+ }
3325
+ function readState() {
3326
+ try {
3327
+ const raw = fs4.readFileSync(getStateFilePath(), "utf-8");
3328
+ return JSON.parse(raw);
3329
+ } catch {
3330
+ return null;
3331
+ }
3332
+ }
3333
+ function writeState(state) {
3334
+ try {
3335
+ const dir = path4.dirname(getStateFilePath());
3336
+ fs4.mkdirSync(dir, { recursive: true, mode: 448 });
3337
+ fs4.writeFileSync(getStateFilePath(), JSON.stringify(state));
3338
+ } catch {
3339
+ }
3340
+ }
3341
+ function compareVersions(a, b) {
3342
+ const pa = a.split(".").map(Number);
3343
+ const pb = b.split(".").map(Number);
3344
+ for (let i = 0; i < 3; i++) {
3345
+ const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
3346
+ if (diff !== 0) return diff;
3347
+ }
2360
3348
  return 0;
2361
3349
  }
2362
3350
  function detectInstallInfo() {
@@ -2489,29 +3477,30 @@ function stopPeriodicUpdateCheck() {
2489
3477
  }
2490
3478
 
2491
3479
  // src/orchestrator-app.tsx
2492
- var import_jsx_runtime10 = __toESM(require_jsx_runtime(), 1);
3480
+ var import_jsx_runtime15 = __toESM(require_jsx_runtime(), 1);
2493
3481
  function BossApp(props) {
2494
3482
  const theme = loadTheme("dark");
2495
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(TerminalSizeProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ThemeContext.Provider, { value: theme, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(AnimationProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(BossAppInner, { ...props }) }) }) });
3483
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(TerminalSizeProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ThemeContext.Provider, { value: theme, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(AnimationProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(BossAppInner, { ...props }) }) }) });
2496
3484
  }
2497
- function BossAppInner({ boss, resetUI }) {
3485
+ function BossAppInner({ boss, resetUI, terminalHistoryPrinter }) {
2498
3486
  const state = useBossState();
3487
+ const theme = useTheme();
2499
3488
  const { exit } = use_app_default();
2500
- const { stdout } = use_stdout_default();
2501
- const { resizeKey, columns, rows } = useTerminalSize();
2502
- const runStartRef = (0, import_react10.useRef)(null);
3489
+ const { stdout, write: writeStdout } = use_stdout_default();
3490
+ const { columns, rows } = useTerminalSize();
3491
+ const runStartRef = (0, import_react15.useRef)(null);
2503
3492
  runStartRef.current = state.runStartMs;
2504
- const charCountRef = (0, import_react10.useRef)(0);
3493
+ const charCountRef = (0, import_react15.useRef)(0);
2505
3494
  charCountRef.current = state.streaming?.text.length ?? 0;
2506
- const realTokensAccumRef = (0, import_react10.useRef)(0);
3495
+ const realTokensAccumRef = (0, import_react15.useRef)(0);
2507
3496
  realTokensAccumRef.current = state.bossInputTokens;
2508
- const [lastUserMessage, setLastUserMessage] = (0, import_react10.useState)("");
3497
+ const [lastUserMessage, setLastUserMessage] = (0, import_react15.useState)("");
2509
3498
  const overlay = state.overlay;
2510
- const [currentRadio, setCurrentRadio] = (0, import_react10.useState)(() => getCurrentStation());
2511
- const [updatePending, setUpdatePending] = (0, import_react10.useState)(
3499
+ const [currentRadio, setCurrentRadio] = (0, import_react15.useState)(() => getCurrentStation());
3500
+ const [updatePending, setUpdatePending] = (0, import_react15.useState)(
2512
3501
  () => getPendingUpdate(VERSION) !== null
2513
3502
  );
2514
- (0, import_react10.useEffect)(() => {
3503
+ (0, import_react15.useEffect)(() => {
2515
3504
  startPeriodicUpdateCheck(VERSION, (msg) => {
2516
3505
  bossStore.appendUpdateNotice(msg);
2517
3506
  setUpdatePending(true);
@@ -2519,8 +3508,8 @@ function BossAppInner({ boss, resetUI }) {
2519
3508
  return () => stopPeriodicUpdateCheck();
2520
3509
  }, []);
2521
3510
  const workersRunning = state.workers.filter((w) => w.status === "working").length;
2522
- const titlePrevRef = (0, import_react10.useRef)("");
2523
- (0, import_react10.useEffect)(() => {
3511
+ const titlePrevRef = (0, import_react15.useRef)("");
3512
+ (0, import_react15.useEffect)(() => {
2524
3513
  if (!stdout) return;
2525
3514
  let title;
2526
3515
  if (workersRunning > 0) {
@@ -2536,45 +3525,83 @@ function BossAppInner({ boss, resetUI }) {
2536
3525
  stdout.write(`\x1B]0;${title}\x1B\\`);
2537
3526
  }
2538
3527
  }, [stdout, workersRunning, state.phase]);
2539
- (0, import_react10.useEffect)(() => {
3528
+ (0, import_react15.useEffect)(() => {
2540
3529
  return () => {
2541
3530
  stdout?.write(`\x1B]0;EZ Boss\x1B\\`);
2542
3531
  };
2543
3532
  }, [stdout]);
2544
- const staticItems = (0, import_react10.useMemo)(
2545
- () => [{ kind: "banner", id: "banner" }, ...state.history],
2546
- [state.history]
3533
+ const liveItems = state.liveItems;
3534
+ const terminalHistoryPrinterRef = (0, import_react15.useRef)(
3535
+ terminalHistoryPrinter ?? createBossTerminalHistoryPrinter({ stream: stdout })
2547
3536
  );
2548
- const openOverlay = (0, import_react10.useCallback)(
3537
+ const terminalHistoryContext = {
3538
+ theme,
3539
+ columns,
3540
+ version: VERSION,
3541
+ model: state.bossModel,
3542
+ provider: state.bossProvider,
3543
+ cwd: process.cwd()
3544
+ };
3545
+ const printedHistoryIdsRef = (0, import_react15.useRef)(/* @__PURE__ */ new Set());
3546
+ (0, import_react15.useEffect)(() => {
3547
+ const printer = terminalHistoryPrinterRef.current;
3548
+ const pending = state.history.filter((item) => !printedHistoryIdsRef.current.has(item.id));
3549
+ if (pending.length === 0) return;
3550
+ printer.print(pending, terminalHistoryContext, { write: writeStdout });
3551
+ for (const item of pending) printedHistoryIdsRef.current.add(item.id);
3552
+ }, [columns, state.bossModel, state.bossProvider, state.history, theme, writeStdout]);
3553
+ const overlayResetTimerRef = (0, import_react15.useRef)(null);
3554
+ (0, import_react15.useEffect)(() => {
3555
+ return () => {
3556
+ if (overlayResetTimerRef.current) clearTimeout(overlayResetTimerRef.current);
3557
+ };
3558
+ }, []);
3559
+ const scheduleOverlayReset = (0, import_react15.useCallback)(() => {
3560
+ if (!resetUI) return;
3561
+ if (overlayResetTimerRef.current) clearTimeout(overlayResetTimerRef.current);
3562
+ overlayResetTimerRef.current = setTimeout(() => {
3563
+ overlayResetTimerRef.current = null;
3564
+ resetUI();
3565
+ }, 0);
3566
+ }, [resetUI]);
3567
+ const openOverlay = (0, import_react15.useCallback)(
2549
3568
  (next) => {
2550
3569
  bossStore.setOverlay(next);
2551
- if (resetUI) resetUI();
3570
+ scheduleOverlayReset();
2552
3571
  },
2553
- [resetUI]
3572
+ [scheduleOverlayReset]
2554
3573
  );
2555
- const closeOverlay = (0, import_react10.useCallback)(() => {
3574
+ const closeOverlay = (0, import_react15.useCallback)(() => {
2556
3575
  bossStore.setOverlay(null);
2557
- if (resetUI) resetUI();
2558
- }, [resetUI]);
3576
+ scheduleOverlayReset();
3577
+ }, [scheduleOverlayReset]);
2559
3578
  void stdout;
2560
3579
  const handleDoubleExit = useDoublePress(
2561
3580
  (pending) => bossStore.setExitPending(pending),
2562
3581
  () => exit()
2563
3582
  );
2564
- (0, import_react10.useEffect)(() => {
3583
+ (0, import_react15.useEffect)(() => {
2565
3584
  if (state.pendingFlush.length > 0) {
2566
3585
  bossStore.commitPendingFlush();
2567
3586
  }
2568
3587
  }, [state.flushGeneration, state.pendingFlush.length]);
3588
+ const handleAbort = (0, import_react15.useCallback)(() => {
3589
+ if (state.phase === "working") {
3590
+ boss.abort();
3591
+ return;
3592
+ }
3593
+ handleDoubleExit();
3594
+ }, [boss, handleDoubleExit, state.phase]);
2569
3595
  use_input_default((input, key) => {
3596
+ if (key.ctrl && input === "c" && overlay) {
3597
+ handleAbort();
3598
+ return;
3599
+ }
2570
3600
  if (key.ctrl && input === "t") {
2571
3601
  if (overlay === "tasks") closeOverlay();
2572
3602
  else openOverlay("tasks");
2573
3603
  return;
2574
3604
  }
2575
- if (key.escape && state.phase === "working") {
2576
- boss.abort();
2577
- }
2578
3605
  });
2579
3606
  const handleSlashCommand = async (value) => {
2580
3607
  const parsed = parseSlash(value);
@@ -2591,7 +3618,7 @@ function BossAppInner({ boss, resetUI }) {
2591
3618
  return true;
2592
3619
  case "clear":
2593
3620
  bossStore.clearHistory();
2594
- resetUI?.();
3621
+ resetUI?.("session-clear");
2595
3622
  await boss.resetConversation();
2596
3623
  bossStore.appendInfo("Session cleared.", "info");
2597
3624
  return true;
@@ -2622,11 +3649,11 @@ function BossAppInner({ boss, resetUI }) {
2622
3649
  }
2623
3650
  const provider = value.slice(0, colon);
2624
3651
  const model = value.slice(colon + 1);
2625
- if (overlay === "model-boss") {
2626
- void boss.switchBossModel(provider, model);
2627
- } else if (overlay === "model-workers") {
2628
- void boss.switchWorkerModel(provider, model);
2629
- }
3652
+ const switchPromise = overlay === "model-boss" ? boss.switchBossModel(provider, model) : overlay === "model-workers" ? boss.switchWorkerModel(provider, model) : Promise.resolve();
3653
+ void switchPromise.catch((err) => {
3654
+ const message = err instanceof Error ? err.message : String(err);
3655
+ bossStore.appendInfo(`Model switch failed: ${message}`, "error");
3656
+ });
2630
3657
  closeOverlay();
2631
3658
  };
2632
3659
  const handleSubmit = (value) => {
@@ -2636,141 +3663,117 @@ function BossAppInner({ boss, resetUI }) {
2636
3663
  void handleSlashCommand(trimmed);
2637
3664
  return;
2638
3665
  }
2639
- bossStore.appendUser(trimmed);
2640
- setLastUserMessage(trimmed);
2641
- const scoped = scopePrefix2(state.scope) + trimmed;
2642
- boss.enqueueUserMessage(scoped);
2643
- };
2644
- const handleAbort = () => {
2645
- if (state.phase === "working") {
2646
- boss.abort();
2647
- return;
2648
- }
2649
- handleDoubleExit();
2650
- };
2651
- if (rows < 14) {
2652
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "column", width: columns, paddingX: 1, marginTop: 1, children: [
2653
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { bold: true, color: COLORS.accent, children: "Terminal too small" }),
2654
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: COLORS.primary, children: `Resize to at least 14 rows to use EZ Boss (currently ${rows}).` })
2655
- ] });
2656
- }
2657
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "column", width: columns, children: [
2658
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Static, { items: staticItems, style: { width: "100%" }, children: (item) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { flexDirection: "column", paddingRight: 1, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StaticRowView, { row: item }) }, item.id) }, resizeKey),
2659
- overlay === "tasks" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(BossTasksOverlay, { boss, workers: state.workers, onClose: closeOverlay }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
2660
- state.streaming && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StreamingTurnView, { turn: state.streaming, isRunning: state.phase === "working" }),
2661
- state.phase === "working" && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2662
- ActivityIndicator,
2663
- {
2664
- phase: state.activityPhase,
2665
- elapsedMs: state.runStartMs ? Date.now() - state.runStartMs : 0,
2666
- runStartRef,
2667
- thinkingMs: state.streaming?.thinkingMs ?? 0,
2668
- isThinking: state.activityPhase === "thinking",
2669
- tokenEstimate: state.bossInputTokens,
2670
- charCountRef,
2671
- realTokensAccumRef,
2672
- userMessage: lastUserMessage,
2673
- activeToolNames: (state.streaming?.tools ?? []).filter((t) => t.status === "running").map((t) => t.name),
2674
- retryInfo: state.retryInfo,
2675
- phrases: BOSS_PHRASES,
2676
- pulseColors: PULSE_COLORS
2677
- }
2678
- ) }),
2679
- state.compaction?.state === "running" && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(CompactionSpinner, {}),
2680
- state.compaction?.state === "done" && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2681
- CompactionDone,
2682
- {
2683
- originalCount: state.compaction.originalCount,
2684
- newCount: state.compaction.newCount,
2685
- tokensBefore: state.compaction.tokensBefore,
2686
- tokensAfter: state.compaction.tokensAfter
2687
- }
2688
- ),
2689
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2690
- InputArea,
2691
- {
2692
- onSubmit: handleSubmit,
2693
- onAbort: handleAbort,
2694
- disabled: state.phase === "working",
2695
- isActive: !overlay,
2696
- cwd: process.cwd(),
2697
- commands: BOSS_SLASH_COMMANDS,
2698
- scopeBadge: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ScopePill, { scope: state.scope }),
2699
- disableMouseTracking: true,
2700
- onTab: () => bossStore.cycleScope(),
2701
- onShiftTab: () => {
2702
- const next = state.bossThinkingLevel ? void 0 : "medium";
2703
- void boss.setBossThinking(next);
2704
- }
2705
- }
2706
- ),
2707
- overlay === "model-boss" || overlay === "model-workers" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2708
- ModelSelector,
2709
- {
2710
- onSelect: handleModelSelect,
2711
- onCancel: closeOverlay,
2712
- loggedInProviders: state.loggedInProviders,
2713
- currentModel: overlay === "model-boss" ? state.bossModel : state.workerModel,
2714
- currentProvider: overlay === "model-boss" ? state.bossProvider : state.workerProvider
2715
- }
2716
- ) : overlay === "radio" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2717
- RadioPicker,
2718
- {
2719
- currentStationId: currentRadio,
2720
- onCancel: closeOverlay,
2721
- onSelect: (value) => {
2722
- if (value === "off") {
2723
- stopRadio();
2724
- setCurrentRadio(null);
2725
- bossStore.appendInfo("Radio off.", "info");
2726
- } else {
2727
- const result = playRadio(value);
2728
- if (result.ok) {
2729
- setCurrentRadio(value);
2730
- const station = RADIO_STATIONS.find((s) => s.id === value);
2731
- bossStore.appendInfo(`Now playing: ${station?.name ?? value}`, "info");
2732
- } else {
2733
- bossStore.appendInfo(result.error ?? "Radio failed to start.", "warning");
2734
- }
2735
- }
2736
- closeOverlay();
3666
+ const userItem = bossStore.createUserItem(trimmed);
3667
+ terminalHistoryPrinterRef.current.print([userItem], terminalHistoryContext, {
3668
+ write: writeStdout
3669
+ });
3670
+ printedHistoryIdsRef.current.add(userItem.id);
3671
+ bossStore.queueSubmittedUserItem(userItem);
3672
+ setLastUserMessage(trimmed);
3673
+ const scoped = scopePrefix2(state.scope) + trimmed;
3674
+ boss.enqueueUserMessage(scoped);
3675
+ };
3676
+ const activityVisible = state.phase === "working" && state.activityPhase !== "idle";
3677
+ const stallStatusVisible = false;
3678
+ const doneStatus = null;
3679
+ const statusSlotVisible = activityVisible || stallStatusVisible || !!doneStatus;
3680
+ const controlsRows = 7 + (statusSlotVisible ? 1 : 0) + (state.exitPending ? 0 : 1);
3681
+ const availableLiveRows = Math.max(1, rows - controlsRows);
3682
+ if (rows < 14) {
3683
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Box_default, { flexDirection: "column", width: columns, paddingX: 1, marginTop: 1, children: [
3684
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { bold: true, color: COLORS.accent, children: "Terminal too small" }),
3685
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: COLORS.primary, children: `Resize to at least 14 rows to use EZ Boss (currently ${rows}).` })
3686
+ ] });
3687
+ }
3688
+ const lastPendingHistoryItem = state.pendingFlush[state.pendingFlush.length - 1];
3689
+ const lastHistoryItem = state.history[state.history.length - 1];
3690
+ const livePane = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3691
+ BossStreamingTurnView,
3692
+ {
3693
+ turn: state.streaming,
3694
+ isRunning: state.phase === "working",
3695
+ liveItems,
3696
+ lastPendingHistoryItem,
3697
+ lastHistoryItem,
3698
+ availableTerminalHeight: availableLiveRows
3699
+ }
3700
+ );
3701
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3702
+ BossChatScreen,
3703
+ {
3704
+ boss,
3705
+ columns,
3706
+ state,
3707
+ overlay,
3708
+ livePane,
3709
+ theme,
3710
+ statusSlotVisible,
3711
+ activityVisible,
3712
+ stallStatusVisible,
3713
+ doneStatus,
3714
+ elapsedMs: state.runStartMs ? Date.now() - state.runStartMs : 0,
3715
+ runStartRef,
3716
+ charCountRef,
3717
+ realTokensAccumRef,
3718
+ lastUserMessage,
3719
+ activeToolNames: (state.streaming?.tools ?? []).filter((tool) => tool.status === "running").map((tool) => tool.name),
3720
+ inputActive: !overlay,
3721
+ isRunning: state.phase === "working",
3722
+ onSubmit: handleSubmit,
3723
+ onAbort: handleAbort,
3724
+ onTab: () => bossStore.cycleScope(),
3725
+ onShiftTab: () => {
3726
+ const next = getNextThinkingLevel(
3727
+ state.bossProvider,
3728
+ state.bossModel,
3729
+ state.bossThinkingLevel
3730
+ );
3731
+ void boss.setBossThinking(next);
3732
+ },
3733
+ commands: BOSS_SLASH_COMMANDS,
3734
+ scopeBadge: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ScopePill, { scope: state.scope }),
3735
+ onCloseOverlay: closeOverlay,
3736
+ onModelSelect: handleModelSelect,
3737
+ currentRadio,
3738
+ onRadioSelect: (value) => {
3739
+ if (value === "off") {
3740
+ stopRadio();
3741
+ setCurrentRadio(null);
3742
+ bossStore.appendInfo("Radio off.", "info");
3743
+ } else {
3744
+ const result = playRadio(value);
3745
+ if (result.ok) {
3746
+ setCurrentRadio(value);
3747
+ const station = RADIO_STATIONS.find((stationInfo) => stationInfo.id === value);
3748
+ bossStore.appendInfo(`Now playing: ${station?.name ?? value}`, "info");
3749
+ } else {
3750
+ bossStore.appendInfo(result.error ?? "Radio failed to start.", "warning");
2737
3751
  }
2738
3752
  }
2739
- ) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
2740
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2741
- BossFooter,
2742
- {
2743
- bossModel: state.bossModel,
2744
- workerModel: state.workerModel,
2745
- tokensIn: state.bossInputTokens,
2746
- exitPending: state.exitPending,
2747
- bossThinkingLevel: state.bossThinkingLevel,
2748
- updatePending,
2749
- currentRadioStationId: currentRadio
2750
- }
2751
- ),
2752
- !state.exitPending && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2753
- WorkerStatusBar,
2754
- {
2755
- workers: state.workers,
2756
- pendingMessages: state.pendingUserMessages
2757
- }
2758
- )
2759
- ] })
2760
- ] })
2761
- ] });
3753
+ closeOverlay();
3754
+ },
3755
+ bossModel: state.bossModel,
3756
+ workerModel: state.workerModel,
3757
+ updatePending,
3758
+ currentRadioStationId: currentRadio,
3759
+ radioStations: RADIO_STATIONS,
3760
+ workers: state.workers,
3761
+ pendingMessages: state.pendingUserMessages,
3762
+ formatDuration: formatBossDuration
3763
+ }
3764
+ );
2762
3765
  }
2763
3766
  function ScopePill({ scope }) {
2764
3767
  const theme = useTheme();
2765
3768
  const isAll = scope === "all";
2766
3769
  const bg = isAll ? COLORS.accent : projectColor(scope);
2767
3770
  const label = isAll ? "All" : scope;
2768
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { children: [
2769
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: theme.textDim, children: "Project " }),
2770
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: "black", backgroundColor: bg, bold: true, children: ` ${label} ` }),
2771
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { color: theme.textDim, children: [
3771
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Text, { children: [
3772
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: theme.textDim, children: "Project " }),
3773
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: "black", backgroundColor: bg, bold: true, children: ` ${label} ` }),
3774
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Text, { color: theme.textDim, children: [
2772
3775
  " ",
2773
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: theme.primary, children: "Tab" }),
3776
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: theme.primary, children: "Tab" }),
2774
3777
  " to switch"
2775
3778
  ] })
2776
3779
  ] });
@@ -2779,396 +3782,89 @@ function scopePrefix2(scope) {
2779
3782
  if (scope === "all") return "[scope:all] ";
2780
3783
  return `[scope:${scope}] `;
2781
3784
  }
2782
- var SHIMMER_WIDTH = 3;
2783
- function formatElapsed(ms) {
2784
- const total = Math.floor(ms / 1e3);
2785
- const m = Math.floor(total / 60);
2786
- const s = total % 60;
2787
- return `${m}:${s.toString().padStart(2, "0")}`;
2788
- }
2789
- function AnimationActiveSentinel() {
2790
- useAnimationActive();
2791
- return null;
2792
- }
2793
- function ShimmerName({
2794
- name,
2795
- color,
2796
- tick
2797
- }) {
2798
- const cycle = name.length + SHIMMER_WIDTH * 2;
2799
- const shimmerPos = tick % cycle - SHIMMER_WIDTH;
2800
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { children: name.split("").map((ch, i) => {
2801
- const isBright = Math.abs(i - shimmerPos) <= SHIMMER_WIDTH;
2802
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color, bold: isBright, dimColor: !isBright, children: ch }, i);
2803
- }) });
2804
- }
2805
- function WorkerStatusBar({
2806
- workers,
2807
- pendingMessages
2808
- }) {
2809
- const theme = useTheme();
2810
- const { columns } = useTerminalSize();
2811
- const working = workers.filter((w) => w.status === "working");
2812
- const errored = workers.filter((w) => w.status === "error");
2813
- const idleCount = workers.length - working.length - errored.length;
2814
- const anyWorking = working.length > 0;
2815
- const tick = useAnimationTick();
2816
- const now = Date.now();
2817
- if (workers.length === 0) return null;
2818
- const slots = [];
2819
- for (const w of working) {
2820
- const projectHue = projectColor(w.name);
2821
- const elapsed = w.workStartedAt ? formatElapsed(now - w.workStartedAt) : null;
2822
- slots.push(
2823
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react10.default.Fragment, { children: [
2824
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ShimmerName, { name: w.name, color: projectHue, tick }),
2825
- elapsed && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { color: theme.textDim, children: [
2826
- " ",
2827
- elapsed
2828
- ] })
2829
- ] }, `w-${w.name}`)
2830
- );
2831
- }
2832
- for (const w of errored) {
2833
- slots.push(
2834
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react10.default.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { color: theme.error, children: [
2835
- "\u2717 ",
2836
- w.name
2837
- ] }) }, `e-${w.name}`)
2838
- );
2839
- }
2840
- if (idleCount > 0) {
2841
- slots.push(
2842
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react10.default.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { color: theme.textDim, children: [
2843
- "\u25CB ",
2844
- idleCount,
2845
- " idle"
2846
- ] }) }, "idle")
2847
- );
2848
- }
2849
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { paddingX: 1, width: columns, flexShrink: 1, children: [
2850
- anyWorking && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(AnimationActiveSentinel, {}),
2851
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { wrap: "truncate", children: [
2852
- slots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react10.default.Fragment, { children: [
2853
- i > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: theme.border, children: " \u2502 " }),
2854
- slot
2855
- ] }, i)),
2856
- pendingMessages > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
2857
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: theme.textDim, children: " " }),
2858
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { color: theme.warning, children: [
2859
- pendingMessages,
2860
- " message",
2861
- pendingMessages === 1 ? "" : "s",
2862
- " queued"
2863
- ] })
2864
- ] })
2865
- ] })
2866
- ] });
2867
- }
2868
- function StaticRowView({ row }) {
2869
- if (row.kind === "banner") {
2870
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { paddingX: 1, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(BossBanner, { subtitle: "Orchestrator", showShortcuts: true }) });
2871
- }
2872
- if (row.kind === "user") return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(UserMessage, { text: row.text });
2873
- if (row.kind === "assistant") return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(AssistantRow, { item: row });
2874
- if (row.kind === "tool") return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ToolHistoryRow, { item: row });
2875
- if (row.kind === "worker_event") return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(WorkerEventRow, { item: row });
2876
- if (row.kind === "worker_error") return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(WorkerErrorRow, { item: row });
2877
- if (row.kind === "info") return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(InfoRow, { text: row.text, level: row.level ?? "info" });
2878
- if (row.kind === "task_dispatch") return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(TaskDispatchRow, { tasks: row.tasks });
2879
- if (row.kind === "update_notice") return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(UpdateNoticeRow, { text: row.text });
2880
- return null;
2881
- }
2882
- function UpdateNoticeRow({ text }) {
2883
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { marginTop: 1, flexShrink: 1, borderStyle: "round", borderColor: COLORS.accent, paddingX: 1, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { wrap: "wrap", children: [
2884
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: COLORS.accent, bold: true, children: "\u2728 " }),
2885
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: COLORS.primary, bold: true, children: text })
2886
- ] }) });
2887
- }
2888
- function TaskDispatchRow({
2889
- tasks
2890
- }) {
2891
- const theme = useTheme();
2892
- const count = tasks.length;
2893
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [
2894
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { children: [
2895
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: COLORS.primary, bold: true, children: "\u23FA " }),
2896
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { color: theme.text, bold: true, children: [
2897
- "Running ",
2898
- count,
2899
- " task",
2900
- count === 1 ? "" : "s",
2901
- ":"
2902
- ] })
2903
- ] }),
2904
- tasks.map((t, i) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { children: [
2905
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: theme.textDim, children: " \u2022 " }),
2906
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: projectColor(t.project), bold: true, children: t.project }),
2907
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: theme.textDim, children: ": " }),
2908
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: theme.text, children: t.title })
2909
- ] }, `${t.project}-${i}`))
2910
- ] });
2911
- }
2912
- var SHORTCUT_PATTERNS = [
2913
- // Modifier+Key combos: Ctrl+T, Shift+Tab, Cmd+K, Ctrl+Shift+P, Ctrl+C
2914
- /\b(?:Ctrl|Cmd|Alt|Option|Opt|Shift|Meta|Win|Super)(?:\s*\+\s*(?:Ctrl|Cmd|Alt|Option|Opt|Shift|Meta|Win|Super))*\s*\+\s*(?:Tab|Enter|Esc|Escape|Space|Backspace|Delete|Del|Home|End|PageUp|PageDown|Up|Down|Left|Right|F[1-9]|F1[0-2]|[A-Z0-9]|\/|\?|\.|,|;|=|-)\b/g,
2915
- // Bare named keys (only when surrounded by clear key context)
2916
- /\b(?:Ctrl-[A-Z]|F[1-9]|F1[0-2])\b/g
2917
- ];
2918
- function highlightShortcuts(text) {
2919
- if (!text) return text;
2920
- const SENTINEL = "\uE000";
2921
- const masks = [];
2922
- let masked = text.replace(/```[\s\S]*?```|`[^`]+`/g, (m) => {
2923
- const idx = masks.push(m) - 1;
2924
- return `${SENTINEL}${idx}${SENTINEL}`;
2925
- });
2926
- for (const re of SHORTCUT_PATTERNS) {
2927
- masked = masked.replace(re, (m) => `\`${m}\``);
2928
- }
2929
- return masked.replace(
2930
- new RegExp(`${SENTINEL}(\\d+)${SENTINEL}`, "g"),
2931
- (_, i) => masks[Number(i)]
2932
- );
2933
- }
2934
- function AssistantRow({ item }) {
2935
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2936
- AssistantMessage,
2937
- {
2938
- text: highlightShortcuts(item.text),
2939
- thinking: item.thinking,
2940
- thinkingMs: item.thinkingMs
2941
- }
2942
- );
3785
+ function formatBossDuration(durationMs) {
3786
+ const total = Math.max(0, Math.floor(durationMs / 1e3));
3787
+ const minutes = Math.floor(total / 60);
3788
+ const seconds = total % 60;
3789
+ return minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`;
2943
3790
  }
2944
- function ToolHistoryRow({ item }) {
2945
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2946
- ToolExecution,
2947
- {
2948
- status: "done",
2949
- name: item.name,
2950
- args: item.args,
2951
- result: item.result,
2952
- isError: item.isError,
2953
- details: item.details,
2954
- formatters: bossToolFormatters
3791
+ var INK_OPTIONS = {
3792
+ // Match ezcoder's keyboard setup: enable kitty keyboard so Ink can decode
3793
+ // enhanced key events, but keep exitOnCtrlC false so our handlers receive it.
3794
+ kittyKeyboard: {
3795
+ mode: "enabled",
3796
+ flags: ["disambiguateEscapeCodes"]
3797
+ },
3798
+ exitOnCtrlC: false
3799
+ };
3800
+ var DISABLE_MODIFY_OTHER_KEYS = "\x1B[>4;0m";
3801
+ var DISABLE_FOCUS_REPORTING = "\x1B[?1004l";
3802
+ var SCREEN_CLEAR = DISABLE_MODIFY_OTHER_KEYS + "\x1B[2J\x1B[3J\x1B[H";
3803
+ var VIEWPORT_CLEAR = DISABLE_MODIFY_OTHER_KEYS + "\x1B[2J\x1B[H";
3804
+ function renderBossApp(opts) {
3805
+ const terminalHistoryPrinter = createBossTerminalHistoryPrinter({ stream: process.stdout });
3806
+ process.stdout.write(SCREEN_CLEAR);
3807
+ const onProcessExit = () => {
3808
+ try {
3809
+ process.stdout.write(DISABLE_MODIFY_OTHER_KEYS + DISABLE_FOCUS_REPORTING);
3810
+ } catch {
2955
3811
  }
2956
- );
2957
- }
2958
- function parseStatusGrade(text) {
2959
- const matches = [...text.matchAll(/^\s*Status:\s*(DONE|UNVERIFIED|PARTIAL|BLOCKED|INFO)\b/gim)];
2960
- const last = matches[matches.length - 1];
2961
- if (!last) return null;
2962
- return last[1].toUpperCase();
2963
- }
2964
- function parseWorkerTrailer(text) {
2965
- const out = {};
2966
- const grab = (label) => {
2967
- const re = new RegExp(
2968
- `^\\s*${label}:\\s*([\\s\\S]*?)(?=^\\s*(?:Changed|Skipped|Verified|Notes|Status):|$)`,
2969
- "im"
2970
- );
2971
- const m = re.exec(text);
2972
- if (!m) return void 0;
2973
- const v = m[1].replace(/```[\s\S]*?```/g, "[code]").replace(/`([^`]+)`/g, "$1").replace(/\s+/g, " ").trim();
2974
- return v.length > 0 ? v : void 0;
2975
3812
  };
2976
- out.changed = grab("Changed");
2977
- out.skipped = grab("Skipped");
2978
- out.verified = grab("Verified");
2979
- out.notes = grab("Notes");
2980
- return out;
2981
- }
2982
- function clip(text, maxLen) {
2983
- return text.length <= maxLen ? text : text.slice(0, Math.max(1, maxLen - 1)) + "\u2026";
2984
- }
2985
- function summarizeFinalText(text, maxLen) {
2986
- if (!text) return "";
2987
- const trailer = parseWorkerTrailer(text);
2988
- const parts = [];
2989
- if (trailer.changed) parts.push(`Changed: ${trailer.changed}`);
2990
- if (trailer.verified) parts.push(`Verified: ${trailer.verified}`);
2991
- if (trailer.skipped) parts.push(`Skipped: ${trailer.skipped}`);
2992
- if (trailer.notes) parts.push(`Notes: ${trailer.notes}`);
2993
- if (parts.length > 0) return clip(parts.join(" \xB7 "), maxLen);
2994
- const beforeSummary = text.split(/^Changed:|^Skipped:|^Verified:|^Notes:|^Status:/im)[0];
2995
- const stripped = beforeSummary.replace(/```[\s\S]*?```/g, "[code]").replace(/`([^`]+)`/g, "$1").replace(/\*\*([^*]+)\*\*/g, "$1").replace(/\*([^*]+)\*/g, "$1").replace(/^\s*[-*]\s+/gm, "").replace(/^#+\s+/gm, "").replace(/\s+/g, " ").trim();
2996
- if (!stripped) return "";
2997
- const firstSentence = stripped.match(/^[^.!?\n]+[.!?]/);
2998
- return clip(firstSentence ? firstSentence[0] : stripped, maxLen);
2999
- }
3000
- function statusGradeColor(grade, theme) {
3001
- switch (grade) {
3002
- case "DONE":
3003
- return theme.success;
3004
- case "UNVERIFIED":
3005
- case "PARTIAL":
3006
- return theme.warning;
3007
- case "BLOCKED":
3008
- return theme.error;
3009
- case "INFO":
3010
- return theme.textDim;
3011
- default:
3012
- return theme.textDim;
3013
- }
3014
- }
3015
- function WorkerEventRow({ item }) {
3016
- const theme = useTheme();
3017
- const { columns } = useTerminalSize();
3018
- const failedCount = item.toolsUsed.filter((t) => !t.ok).length;
3019
- const total = item.toolsUsed.length;
3020
- const grade = parseStatusGrade(item.finalText);
3021
- const loaderStatus = grade === "BLOCKED" || failedCount > 0 ? "error" : grade === "UNVERIFIED" || grade === "PARTIAL" ? "queued" : "done";
3022
- const headerColor = loaderStatus === "error" ? theme.toolError : projectColor(item.project);
3023
- const toolSummary = total === 0 ? "no tools" : failedCount > 0 ? `${total} tools (${failedCount} failed)` : `${total} tool${total === 1 ? "" : "s"}`;
3024
- const fieldMaxLen = Math.max(20, columns - 14);
3025
- const trailer = parseWorkerTrailer(item.finalText);
3026
- const hasTrailer = !!(trailer.changed || trailer.skipped || trailer.verified || trailer.notes);
3027
- const fallbackSummary = hasTrailer ? "" : summarizeFinalText(item.finalText, fieldMaxLen);
3028
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "column", marginTop: 1, children: [
3029
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "row", children: [
3030
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ToolUseLoader, { status: loaderStatus }),
3031
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { flexGrow: 1, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { wrap: "wrap", children: [
3032
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: headerColor, bold: true, children: item.project }),
3033
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: theme.text, children: ` turn ${item.turnIndex}` }),
3034
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: theme.textDim, children: ` \xB7 ${toolSummary}` }),
3035
- grade && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
3036
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: theme.textDim, children: " \xB7 " }),
3037
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: statusGradeColor(grade, theme), bold: true, children: grade })
3038
- ] })
3039
- ] }) })
3040
- ] }),
3041
- hasTrailer ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
3042
- trailer.changed && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(TrailerLine, { label: "Changed", value: trailer.changed, maxLen: fieldMaxLen }),
3043
- trailer.verified && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3044
- TrailerLine,
3045
- {
3046
- label: "Verified",
3047
- value: trailer.verified,
3048
- maxLen: fieldMaxLen,
3049
- labelColor: theme.success
3050
- }
3051
- ),
3052
- trailer.skipped && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3053
- TrailerLine,
3054
- {
3055
- label: "Skipped",
3056
- value: trailer.skipped,
3057
- maxLen: fieldMaxLen,
3058
- labelColor: theme.warning
3059
- }
3060
- ),
3061
- trailer.notes && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(TrailerLine, { label: "Notes", value: trailer.notes, maxLen: fieldMaxLen })
3062
- ] }) : fallbackSummary && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MessageResponse, { children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: theme.textDim, wrap: "truncate", children: fallbackSummary }) })
3063
- ] });
3064
- }
3065
- function TrailerLine({
3066
- label,
3067
- value,
3068
- maxLen,
3069
- labelColor
3070
- }) {
3071
- const theme = useTheme();
3072
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MessageResponse, { children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { wrap: "truncate", children: [
3073
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { color: labelColor ?? theme.textDim, bold: true, children: [
3074
- label,
3075
- ":"
3076
- ] }),
3077
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: theme.text, children: ` ${clip(value, maxLen - label.length - 2)}` })
3078
- ] }) });
3079
- }
3080
- function WorkerErrorRow({ item }) {
3081
- const theme = useTheme();
3082
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "column", marginTop: 1, children: [
3083
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "row", children: [
3084
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ToolUseLoader, { status: "error" }),
3085
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { flexGrow: 1, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { wrap: "wrap", children: [
3086
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: theme.toolError, bold: true, children: item.project }),
3087
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: theme.textDim, children: " worker error" })
3088
- ] }) })
3089
- ] }),
3090
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MessageResponse, { children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: theme.error, wrap: "wrap", children: item.message }) })
3091
- ] });
3092
- }
3093
- function InfoRow({
3094
- text,
3095
- level
3096
- }) {
3097
- if (level === "info") return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(AssistantMessage, { text });
3098
- const theme = useTheme();
3099
- const color = level === "error" ? theme.error : theme.warning;
3100
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { marginTop: 1, flexDirection: "row", children: [
3101
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ToolUseLoader, { status: level === "error" ? "error" : "queued" }),
3102
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { flexGrow: 1, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color, wrap: "wrap", children: text }) })
3103
- ] });
3104
- }
3105
- function StreamingTurnView({
3106
- turn,
3107
- isRunning
3108
- }) {
3109
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "column", children: [
3110
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3111
- StreamingArea,
3112
- {
3113
- isRunning,
3114
- streamingText: turn.text,
3115
- streamingThinking: turn.thinking,
3116
- thinkingMs: turn.thinkingMs
3117
- }
3118
- ),
3119
- turn.tools.map((t) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StreamingToolRow, { tool: t }, t.toolCallId))
3120
- ] });
3121
- }
3122
- function StreamingToolRow({ tool }) {
3123
- if (tool.status === "running") {
3124
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3125
- ToolExecution,
3126
- {
3127
- status: "running",
3128
- name: tool.name,
3129
- args: tool.args,
3130
- formatters: bossToolFormatters
3131
- }
3132
- );
3133
- }
3134
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3135
- ToolExecution,
3136
- {
3137
- status: "done",
3138
- name: tool.name,
3139
- args: tool.args,
3140
- result: tool.result ?? "",
3141
- isError: tool.status === "error",
3142
- details: tool.details,
3143
- formatters: bossToolFormatters
3144
- }
3145
- );
3146
- }
3147
- function renderBossApp(opts) {
3813
+ process.on("exit", onProcessExit);
3148
3814
  const ref = { instance: null };
3149
- const resetUI = () => {
3815
+ const resetUI = (reason = "viewport") => {
3150
3816
  const old = ref.instance;
3151
3817
  if (!old) return;
3152
- process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
3153
3818
  old.unmount();
3154
- ref.instance = render_default(/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(BossApp, { boss: opts.boss, resetUI }), {
3155
- exitOnCtrlC: false
3156
- });
3819
+ if (reason === "resize-redraw") {
3820
+ terminalHistoryPrinter.resetPrinted();
3821
+ process.stdout.write(SCREEN_CLEAR);
3822
+ const snapshot = getBossState();
3823
+ if (snapshot.history.length > 0) {
3824
+ terminalHistoryPrinter.print(snapshot.history, {
3825
+ theme: loadTheme("dark"),
3826
+ columns: Math.max(40, process.stdout.columns ?? 80),
3827
+ version: VERSION,
3828
+ model: snapshot.bossModel,
3829
+ provider: snapshot.bossProvider,
3830
+ cwd: process.cwd()
3831
+ });
3832
+ }
3833
+ } else if (reason === "session-clear") {
3834
+ terminalHistoryPrinter.clear();
3835
+ process.stdout.write(SCREEN_CLEAR);
3836
+ } else {
3837
+ process.stdout.write(VIEWPORT_CLEAR);
3838
+ }
3839
+ ref.instance = render_default(
3840
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3841
+ BossApp,
3842
+ {
3843
+ boss: opts.boss,
3844
+ resetUI,
3845
+ terminalHistoryPrinter
3846
+ }
3847
+ ),
3848
+ INK_OPTIONS
3849
+ );
3157
3850
  };
3158
- const instance = render_default(/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(BossApp, { boss: opts.boss, resetUI }), {
3159
- // Disable Ink's built-in exit-on-Ctrl+C we need our own double-press
3160
- // handler in BossApp to drive the "Press Ctrl+C again to exit" footer
3161
- // message. With this flag true (the default), Ink kills the process on
3162
- // the very first Ctrl+C and InputArea's onAbort never runs.
3163
- exitOnCtrlC: false
3164
- });
3851
+ const instance = render_default(
3852
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(BossApp, { boss: opts.boss, resetUI, terminalHistoryPrinter }),
3853
+ INK_OPTIONS
3854
+ );
3165
3855
  ref.instance = instance;
3166
3856
  let resizeTimer = null;
3857
+ let resizeListenerEnabled = false;
3858
+ const enableResizeListener = setTimeout(() => {
3859
+ resizeListenerEnabled = true;
3860
+ }, 1e3);
3167
3861
  const onTerminalResize = () => {
3862
+ if (!resizeListenerEnabled) return;
3168
3863
  if (resizeTimer) clearTimeout(resizeTimer);
3169
3864
  resizeTimer = setTimeout(() => {
3170
3865
  resizeTimer = null;
3171
- resetUI();
3866
+ if (getBossState().phase === "working") return;
3867
+ resetUI("resize-redraw");
3172
3868
  }, 250);
3173
3869
  };
3174
3870
  process.stdout.on("resize", onTerminalResize);
@@ -3183,21 +3879,30 @@ function renderBossApp(opts) {
3183
3879
  const current = ref.instance;
3184
3880
  if (!current) {
3185
3881
  process.stdout.off("resize", onTerminalResize);
3882
+ process.off("exit", onProcessExit);
3883
+ clearTimeout(enableResizeListener);
3186
3884
  if (resizeTimer) clearTimeout(resizeTimer);
3885
+ onProcessExit();
3187
3886
  return;
3188
3887
  }
3189
3888
  await current.waitUntilExit();
3190
3889
  if (ref.instance === current) {
3191
3890
  ref.instance = null;
3192
3891
  process.stdout.off("resize", onTerminalResize);
3892
+ process.off("exit", onProcessExit);
3893
+ clearTimeout(enableResizeListener);
3193
3894
  if (resizeTimer) clearTimeout(resizeTimer);
3895
+ onProcessExit();
3194
3896
  return;
3195
3897
  }
3196
3898
  }
3197
3899
  },
3198
3900
  unmount: () => {
3199
3901
  process.stdout.off("resize", onTerminalResize);
3902
+ process.off("exit", onProcessExit);
3903
+ clearTimeout(enableResizeListener);
3200
3904
  if (resizeTimer) clearTimeout(resizeTimer);
3905
+ onProcessExit();
3201
3906
  ref.instance?.unmount();
3202
3907
  }
3203
3908
  };
@@ -3205,8 +3910,8 @@ function renderBossApp(opts) {
3205
3910
 
3206
3911
  // src/splash.tsx
3207
3912
  init_esm_shims();
3208
- var import_react11 = __toESM(require_react(), 1);
3209
- var import_jsx_runtime11 = __toESM(require_jsx_runtime(), 1);
3913
+ var import_react16 = __toESM(require_react(), 1);
3914
+ var import_jsx_runtime16 = __toESM(require_jsx_runtime(), 1);
3210
3915
  var SPLASH_LINES = [
3211
3916
  " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 ",
3212
3917
  " \u2588\u2588\u2588\u2591\u2591\u2591\u2591\u2591\u2588\u2588\u2588 \u2588\u2588\u2588\u2591\u2591\u2591\u2591\u2591\u2588\u2588\u2588 \u2591\u2591\u2588\u2588\u2588\u2591\u2591\u2591\u2591\u2591\u2588\u2588\u2588 ",
@@ -3224,33 +3929,33 @@ function colorForLine(lineIdx, totalLines, offset) {
3224
3929
  return GRADIENT[idx];
3225
3930
  }
3226
3931
  function SplashLogo({ offset }) {
3227
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Box_default, { flexDirection: "column", children: SPLASH_LINES.map((line, i) => {
3932
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Box_default, { flexDirection: "column", children: SPLASH_LINES.map((line, i) => {
3228
3933
  const hue = colorForLine(i, SPLASH_LINES.length, offset);
3229
3934
  const segments = [];
3230
3935
  let buf = "";
3231
3936
  let bufDim = false;
3232
3937
  for (const ch of line) {
3233
- const dim = ch === "\u2591";
3938
+ const dim2 = ch === "\u2591";
3234
3939
  if (segments.length === 0 && buf.length === 0) {
3235
3940
  buf = ch;
3236
- bufDim = dim;
3941
+ bufDim = dim2;
3237
3942
  continue;
3238
3943
  }
3239
- if (dim === bufDim) {
3944
+ if (dim2 === bufDim) {
3240
3945
  buf += ch;
3241
3946
  } else {
3242
3947
  segments.push({ text: buf, dim: bufDim });
3243
3948
  buf = ch;
3244
- bufDim = dim;
3949
+ bufDim = dim2;
3245
3950
  }
3246
3951
  }
3247
3952
  if (buf) segments.push({ text: buf, dim: bufDim });
3248
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { children: segments.map((seg, j) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: hue, dimColor: seg.dim, children: seg.text }, j)) }, i);
3953
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Text, { children: segments.map((seg, j) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Text, { color: hue, dimColor: seg.dim, children: seg.text }, j)) }, i);
3249
3954
  }) });
3250
3955
  }
3251
3956
  function SplashScreen({ caption }) {
3252
- const [offset, setOffset] = (0, import_react11.useState)(0);
3253
- (0, import_react11.useEffect)(() => {
3957
+ const [offset, setOffset] = (0, import_react16.useState)(0);
3958
+ (0, import_react16.useEffect)(() => {
3254
3959
  const timer = setInterval(() => {
3255
3960
  setOffset((o) => o + 1);
3256
3961
  }, 120);
@@ -3258,11 +3963,11 @@ function SplashScreen({ caption }) {
3258
3963
  clearInterval(timer);
3259
3964
  };
3260
3965
  }, []);
3261
- const [size, setSize] = (0, import_react11.useState)(() => ({
3966
+ const [size, setSize] = (0, import_react16.useState)(() => ({
3262
3967
  columns: process.stdout.columns ?? 80,
3263
3968
  rows: process.stdout.rows ?? 24
3264
3969
  }));
3265
- (0, import_react11.useEffect)(() => {
3970
+ (0, import_react16.useEffect)(() => {
3266
3971
  const handler = () => setSize({
3267
3972
  columns: process.stdout.columns ?? 80,
3268
3973
  rows: process.stdout.rows ?? 24
@@ -3274,27 +3979,27 @@ function SplashScreen({ caption }) {
3274
3979
  }, []);
3275
3980
  const SPLASH_BLOCK_HEIGHT = SPLASH_LINES.length + 3;
3276
3981
  const verticalPad = Math.max(0, Math.floor((size.rows - SPLASH_BLOCK_HEIGHT) / 2));
3277
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { flexDirection: "column", width: size.columns, height: size.rows, alignItems: "center", children: [
3278
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Box_default, { height: verticalPad, flexShrink: 0 }),
3279
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { flexDirection: "column", alignItems: "flex-start", flexShrink: 0, children: [
3280
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(SplashLogo, { offset }),
3281
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Box_default, { width: SPLASH_WIDTH, marginTop: 1, justifyContent: "center", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { children: [
3282
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: COLORS.text, bold: true, children: BRAND }),
3283
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { color: COLORS.textDim, children: [
3982
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(Box_default, { flexDirection: "column", width: size.columns, height: size.rows, alignItems: "center", children: [
3983
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Box_default, { height: verticalPad, flexShrink: 0 }),
3984
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(Box_default, { flexDirection: "column", alignItems: "flex-start", flexShrink: 0, children: [
3985
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(SplashLogo, { offset }),
3986
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Box_default, { width: SPLASH_WIDTH, marginTop: 1, justifyContent: "center", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(Text, { children: [
3987
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Text, { color: COLORS.text, bold: true, children: BRAND }),
3988
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(Text, { color: COLORS.textDim, children: [
3284
3989
  " v",
3285
3990
  VERSION
3286
3991
  ] }),
3287
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: COLORS.textDim, children: " \xB7 By " }),
3288
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: COLORS.text, bold: true, children: AUTHOR })
3992
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Text, { color: COLORS.textDim, children: " \xB7 By " }),
3993
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Text, { color: COLORS.text, bold: true, children: AUTHOR })
3289
3994
  ] }) }),
3290
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Box_default, { width: SPLASH_WIDTH, justifyContent: "center", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: COLORS.textDim, children: caption ?? "Spinning up the orchestrator\u2026" }) })
3995
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Box_default, { width: SPLASH_WIDTH, justifyContent: "center", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Text, { color: COLORS.textDim, children: caption ?? "Spinning up the orchestrator\u2026" }) })
3291
3996
  ] })
3292
3997
  ] });
3293
3998
  }
3294
3999
  function showSplash(opts) {
3295
4000
  const start = Date.now();
3296
4001
  void playSplashAudio();
3297
- const instance = render_default(/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(SplashScreen, { caption: opts.caption }));
4002
+ const instance = render_default(/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(SplashScreen, { caption: opts.caption }));
3298
4003
  const audioDurationMs = getSplashAudioDurationMs();
3299
4004
  const defaultMinMs = audioDurationMs + 200;
3300
4005
  return {
@@ -3353,12 +4058,12 @@ function parseArgs(argv) {
3353
4058
  return args;
3354
4059
  }
3355
4060
  function printHelpAndExit() {
3356
- const c = (color, text) => source_default.hex(color)(text);
4061
+ const c = (color2, text) => source_default.hex(color2)(text);
3357
4062
  process.stdout.write(
3358
4063
  "\n" + c(COLORS.primary, "EZ Boss") + c(COLORS.textDim, " \u2014 orchestrator that drives multiple ezcoder workers from one chat.\n\n") + c(COLORS.text, "Usage\n") + " " + c(COLORS.accent, "ezboss") + c(
3359
4064
  COLORS.textDim,
3360
4065
  " start orchestrator using linked projects\n"
3361
- ) + " " + c(COLORS.accent, "ezboss link") + c(COLORS.textDim, " pick which projects to link (interactive)\n") + " " + c(COLORS.accent, "ezboss telegram") + c(COLORS.textDim, " configure Telegram bot integration\n") + " " + c(COLORS.accent, "ezboss serve") + c(COLORS.textDim, " run the boss over Telegram (no TUI)\n") + " " + c(COLORS.accent, "ezboss continue") + c(COLORS.textDim, " resume the most recent boss session\n") + " " + c(COLORS.accent, "ezboss --resume <id>") + c(COLORS.textDim, " resume a specific boss session\n") + " " + c(COLORS.accent, "ezboss --project <spec> [...]") + c(COLORS.textDim, " override links with explicit project(s)\n\n") + c(COLORS.text, "Options\n") + " " + c(COLORS.primary, "--project, -p <spec>") + c(COLORS.textDim, ' project to manage. spec is "cwd" or "name=cwd". repeatable.\n') + " " + c(COLORS.primary, "--boss-model <id>") + c(COLORS.textDim, " model for the orchestrator (default: claude-opus-4-7)\n") + " " + c(COLORS.primary, "--worker-model <id>") + c(COLORS.textDim, " model for workers (default: claude-sonnet-4-6)\n") + " " + c(COLORS.primary, "--help, -h") + c(COLORS.textDim, " show this help\n\n") + c(COLORS.textDim, "Talk to the boss at the prompt. Press ") + c(COLORS.accent, "Ctrl+C") + c(COLORS.textDim, " twice to exit.\n\n")
4066
+ ) + " " + c(COLORS.accent, "ezboss link") + c(COLORS.textDim, " pick which projects to link (interactive)\n") + " " + c(COLORS.accent, "ezboss telegram") + c(COLORS.textDim, " configure Telegram bot integration\n") + " " + c(COLORS.accent, "ezboss serve") + c(COLORS.textDim, " run the boss over Telegram (no TUI)\n") + " " + c(COLORS.accent, "ezboss continue") + c(COLORS.textDim, " resume the most recent boss session\n") + " " + c(COLORS.accent, "ezboss --resume <id>") + c(COLORS.textDim, " resume a specific boss session\n") + " " + c(COLORS.accent, "ezboss --project <spec> [...]") + c(COLORS.textDim, " override links with explicit project(s)\n\n") + c(COLORS.text, "Options\n") + " " + c(COLORS.primary, "--project, -p <spec>") + c(COLORS.textDim, ' project to manage. spec is "cwd" or "name=cwd". repeatable.\n') + " " + c(COLORS.primary, "--boss-model <id>") + c(COLORS.textDim, " model for the orchestrator (default: claude-opus-4-8)\n") + " " + c(COLORS.primary, "--worker-model <id>") + c(COLORS.textDim, " model for workers (default: claude-sonnet-4-6)\n") + " " + c(COLORS.primary, "--help, -h") + c(COLORS.textDim, " show this help\n\n") + c(COLORS.textDim, "Talk to the boss at the prompt. Press ") + c(COLORS.accent, "Ctrl+C") + c(COLORS.textDim, " twice to exit.\n\n")
3362
4067
  );
3363
4068
  process.exit(0);
3364
4069
  }
@@ -3394,7 +4099,7 @@ async function runServeSubcommand(argv) {
3394
4099
  }
3395
4100
  const settings = await loadSettings();
3396
4101
  const bossProvider = settings.bossProvider ?? "anthropic";
3397
- const bossModel = cliBossModel ?? settings.bossModel ?? "claude-opus-4-7";
4102
+ const bossModel = cliBossModel ?? settings.bossModel ?? "claude-opus-4-8";
3398
4103
  const workerProvider = settings.workerProvider ?? "anthropic";
3399
4104
  const workerModel = cliWorkerModel ?? settings.workerModel ?? "claude-sonnet-4-6";
3400
4105
  await runBossServeMode({
@@ -3423,7 +4128,7 @@ async function runOrchestrator(args) {
3423
4128
  });
3424
4129
  const settings = await loadSettings();
3425
4130
  const finalBossProvider = args.bossProvider ?? settings.bossProvider ?? "anthropic";
3426
- const finalBossModel = args.bossModel ?? settings.bossModel ?? "claude-opus-4-7";
4131
+ const finalBossModel = args.bossModel ?? settings.bossModel ?? "claude-opus-4-8";
3427
4132
  const finalWorkerProvider = args.workerProvider ?? settings.workerProvider ?? "anthropic";
3428
4133
  const finalWorkerModel = args.workerModel ?? settings.workerModel ?? "claude-sonnet-4-6";
3429
4134
  initLogger({