@kenkaiiii/gg-boss 4.3.163 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/README.md +3 -3
  2. package/dist/{chunk-JBKZOBJ7.js → chunk-5ENJR6XI.js} +33489 -19470
  3. package/dist/{chunk-JBKZOBJ7.js.map → chunk-5ENJR6XI.js.map} +1 -1
  4. package/dist/{chunk-YNWFCUMR.js → chunk-DZO3FVYX.js} +3 -3
  5. package/dist/chunk-DZO3FVYX.js.map +1 -0
  6. package/dist/{chunk-QT366Y52.js → chunk-JEGMYLRS.js} +3 -3
  7. package/dist/{chunk-WJ4S4TOY.js → chunk-NA54UQR4.js} +2 -2
  8. package/dist/cli.js +1679 -1378
  9. package/dist/cli.js.map +1 -1
  10. package/dist/{devtools-526EIB4G.js → devtools-XF5S3NSL.js} +11 -33
  11. package/dist/{devtools-526EIB4G.js.map → devtools-XF5S3NSL.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-WPYTQADG.js → pixel-OQO4WMWJ.js} +4 -4
  18. package/dist/{pixel-fix-4WGZAJ5W.js → pixel-fix-JKVDORFT.js} +3 -3
  19. package/package.json +6 -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-WPYTQADG.js.map +0 -1
  25. /package/dist/{chunk-QT366Y52.js.map → chunk-JEGMYLRS.js.map} +0 -0
  26. /package/dist/{chunk-WJ4S4TOY.js.map → chunk-NA54UQR4.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-OQO4WMWJ.js.map} +0 -0
  31. /package/dist/{pixel-fix-4WGZAJ5W.js.map → pixel-fix-JKVDORFT.js.map} +0 -0
package/dist/cli.js CHANGED
@@ -1,33 +1,50 @@
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,
6
+ AuthStorage,
7
7
  Box_default,
8
+ ChatControls,
9
+ ChatInputStack,
10
+ ChatLayout,
11
+ ChatLivePane,
8
12
  CompactionDone,
9
13
  CompactionSpinner,
10
14
  GGBoss,
11
15
  InputArea,
12
16
  MODELS,
13
17
  MessageResponse,
14
- ModelSelector,
15
- SelectList,
16
- Static,
17
- StreamingArea,
18
+ RESPONSE_LEFT_PADDING,
19
+ TelegramBot,
18
20
  TerminalSizeProvider,
19
21
  Text,
20
22
  ThemeContext,
21
23
  ToolExecution,
24
+ ToolGroupExecution,
22
25
  ToolUseLoader,
26
+ TranscriptItemFrame,
23
27
  UserMessage,
24
28
  bossStore,
29
+ bossToolGroupRenderers,
30
+ buildToolGroupSummary,
25
31
  closeLogger,
32
+ color,
33
+ createAutoUpdater,
34
+ dim,
35
+ formatHistoryWrite,
26
36
  getAppPaths,
27
37
  getBossState,
28
38
  getContextWindow,
39
+ getDefaultModel,
40
+ getModel,
41
+ getNextThinkingLevel,
29
42
  getSplashAudioDurationMs,
43
+ getTranscriptItemMarginTop,
44
+ gradientLine,
45
+ indent,
30
46
  initLogger,
47
+ isModelLoaded,
31
48
  loadSettings,
32
49
  loadTheme,
33
50
  log,
@@ -36,9 +53,18 @@ import {
36
53
  require_jsx_runtime,
37
54
  require_react,
38
55
  saveSettings,
56
+ serializeCompletedItemToTerminalHistory,
57
+ setProgressCallback,
39
58
  setStreamDiagnostic,
59
+ shouldSeparateTranscriptItemKinds,
60
+ shouldTopSpaceStreamingAssistant,
61
+ stripAnsi,
62
+ stripTerminalFocusSequences,
40
63
  subscribeToBossStore,
41
64
  tasksStore,
65
+ toolTonePalette,
66
+ transcribeVoice,
67
+ truncatePlain,
42
68
  useAnimationActive,
43
69
  useAnimationTick,
44
70
  useBossState,
@@ -48,18 +74,17 @@ import {
48
74
  useTheme,
49
75
  use_app_default,
50
76
  use_input_default,
51
- use_stdout_default
52
- } from "./chunk-JBKZOBJ7.js";
53
- import "./chunk-RMSZMSH5.js";
54
- import "./chunk-EZYGVECW.js";
55
- import "./chunk-QT366Y52.js";
77
+ use_stdout_default,
78
+ wrapPlain
79
+ } from "./chunk-5ENJR6XI.js";
80
+ import "./chunk-JEGMYLRS.js";
56
81
  import {
57
82
  source_default
58
- } from "./chunk-WJ4S4TOY.js";
83
+ } from "./chunk-NA54UQR4.js";
59
84
  import {
60
85
  __toESM,
61
86
  init_esm_shims
62
- } from "./chunk-YNWFCUMR.js";
87
+ } from "./chunk-DZO3FVYX.js";
63
88
 
64
89
  // src/cli.ts
65
90
  init_esm_shims();
@@ -344,7 +369,7 @@ init_esm_shims();
344
369
  // package.json
345
370
  var package_default = {
346
371
  name: "@kenkaiiii/gg-boss",
347
- version: "4.3.163",
372
+ version: "4.4.0",
348
373
  type: "module",
349
374
  description: "Orchestrator agent that drives multiple ggcoder sessions across projects from a single chat",
350
375
  license: "MIT",
@@ -370,11 +395,12 @@ var package_default = {
370
395
  devDependencies: {
371
396
  "@kenkaiiii/gg-agent": "workspace:*",
372
397
  "@kenkaiiii/gg-ai": "workspace:*",
398
+ "@kenkaiiii/gg-core": "workspace:*",
373
399
  "@kenkaiiii/ggcoder": "workspace:*",
374
400
  "@types/node": "^25.6.0",
375
401
  "@types/react": "^19.2.14",
376
402
  chalk: "^5.6.2",
377
- ink: "^7.0.2",
403
+ ink: "6.8.0",
378
404
  react: "^19.2.5",
379
405
  tsup: "^8.5.1",
380
406
  typescript: "^6.0.3",
@@ -422,24 +448,6 @@ var GRADIENT = [
422
448
  "#b91c1c"
423
449
  // red-700 (slight darker tail)
424
450
  ];
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
451
  var COLORS = {
444
452
  primary: "#e11d48",
445
453
  // crimson-rose — main brand color
@@ -502,9 +510,9 @@ function GradientText({ text }) {
502
510
  if (ch === " ") {
503
511
  chars.push(ch);
504
512
  } else {
505
- const color = GRADIENT[colorIdx % GRADIENT.length];
513
+ const color2 = GRADIENT[colorIdx % GRADIENT.length];
506
514
  chars.push(
507
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color, children: ch }, i)
515
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: color2, children: ch }, i)
508
516
  );
509
517
  colorIdx++;
510
518
  }
@@ -670,317 +678,6 @@ async function runLinkCommand() {
670
678
  init_esm_shims();
671
679
  import path3 from "path";
672
680
  import fs3 from "fs/promises";
673
-
674
- // src/voice-transcriber.ts
675
- init_esm_shims();
676
- var TARGET_SAMPLE_RATE = 16e3;
677
- var MODEL_ID = "Xenova/whisper-tiny.en";
678
- var transcriber = null;
679
- var loadPromise = null;
680
- var onProgress = null;
681
- function setProgressCallback(cb) {
682
- onProgress = cb;
683
- }
684
- function resample(audio, fromRate, toRate) {
685
- if (fromRate === toRate) return audio;
686
- const ratio = fromRate / toRate;
687
- const newLength = Math.round(audio.length / ratio);
688
- const result = new Float32Array(newLength);
689
- for (let i = 0; i < newLength; i++) {
690
- const srcIndex = i * ratio;
691
- const low = Math.floor(srcIndex);
692
- const high = Math.min(low + 1, audio.length - 1);
693
- const frac = srcIndex - low;
694
- result[i] = audio[low] * (1 - frac) + audio[high] * frac;
695
- }
696
- return result;
697
- }
698
- function downmixToMono(channelData) {
699
- if (channelData.length === 0) return new Float32Array();
700
- if (channelData.length === 1) return channelData[0];
701
- const samples = channelData[0].length;
702
- const out = new Float32Array(samples);
703
- const scale = 1 / channelData.length;
704
- for (let i = 0; i < samples; i++) {
705
- let mixed = 0;
706
- for (const channel of channelData) mixed += channel[i] ?? 0;
707
- out[i] = mixed * scale;
708
- }
709
- return out;
710
- }
711
- async function decodeOggOpus(buffer) {
712
- const { OggOpusDecoder } = await import("ogg-opus-decoder");
713
- const decoder = new OggOpusDecoder();
714
- await decoder.ready;
715
- try {
716
- const decoded = await decoder.decodeFile(buffer);
717
- if (!decoded.channelData?.length || !decoded.channelData[0]?.length) {
718
- throw new Error("Decoded audio is empty");
719
- }
720
- const mono = downmixToMono(decoded.channelData);
721
- return resample(mono, decoded.sampleRate, TARGET_SAMPLE_RATE);
722
- } finally {
723
- decoder.free();
724
- }
725
- }
726
- async function getTranscriber() {
727
- if (transcriber) return transcriber;
728
- if (!loadPromise) {
729
- loadPromise = (async () => {
730
- const { pipeline } = await import("@huggingface/transformers");
731
- const instance = await pipeline("automatic-speech-recognition", MODEL_ID, {
732
- dtype: "fp32",
733
- progress_callback: onProgress ?? void 0
734
- });
735
- transcriber = instance;
736
- return instance;
737
- })();
738
- }
739
- return loadPromise;
740
- }
741
- function isModelLoaded() {
742
- return transcriber !== null;
743
- }
744
- async function transcribeVoice(fileUrl) {
745
- const response = await fetch(fileUrl);
746
- if (!response.ok) throw new Error(`Failed to download voice file: ${response.status}`);
747
- const buffer = new Uint8Array(await response.arrayBuffer());
748
- const pcm = await decodeOggOpus(buffer);
749
- const asr = await getTranscriber();
750
- const result = await asr(pcm);
751
- const text = Array.isArray(result) ? result[0]?.text : result.text;
752
- return (text ?? "").trim();
753
- }
754
-
755
- // src/telegram.ts
756
- init_esm_shims();
757
- var TELEGRAM_API = "https://api.telegram.org";
758
- var MAX_MESSAGE_LENGTH = 4096;
759
- var TelegramBot = class {
760
- token;
761
- allowedUserId;
762
- offset = 0;
763
- running = false;
764
- onMessage = null;
765
- onVoiceMessage = null;
766
- onCallback = null;
767
- onBotAdded = null;
768
- onBotRemoved = null;
769
- constructor(config) {
770
- this.token = config.botToken;
771
- this.allowedUserId = config.allowedUserId;
772
- }
773
- /** Register handler for incoming text messages. */
774
- onText(handler) {
775
- this.onMessage = handler;
776
- }
777
- /** Register handler for incoming voice notes. */
778
- onVoice(handler) {
779
- this.onVoiceMessage = handler;
780
- }
781
- /** Register handler for inline keyboard button presses. */
782
- onCallbackQuery(handler) {
783
- this.onCallback = handler;
784
- }
785
- /** Register handler for when the bot is added to a group. */
786
- onAddedToGroup(handler) {
787
- this.onBotAdded = handler;
788
- }
789
- /** Register handler for when the bot is removed from a group. */
790
- onRemovedFromGroup(handler) {
791
- this.onBotRemoved = handler;
792
- }
793
- /** Start long polling. Blocks until stop() is called. */
794
- async start() {
795
- this.running = true;
796
- const me = await this.apiCall("getMe");
797
- if (!me.ok) {
798
- throw new Error(`Invalid bot token: ${JSON.stringify(me)}`);
799
- }
800
- while (this.running) {
801
- try {
802
- const updates = await this.getUpdates();
803
- for (const update of updates) {
804
- await this.handleUpdate(update);
805
- }
806
- } catch (err) {
807
- if (!this.running) break;
808
- console.error(`[telegram] Poll error: ${err instanceof Error ? err.message : err}`);
809
- await sleep(3e3);
810
- }
811
- }
812
- }
813
- /** Stop long polling. */
814
- stop() {
815
- this.running = false;
816
- }
817
- /** Send a text message to a specific chat. Converts markdown and splits long messages. */
818
- async send(chatId, text, buttons) {
819
- const converted = toTelegramMarkdown(text);
820
- const chunks = splitMessage(converted);
821
- for (let i = 0; i < chunks.length; i++) {
822
- const isLast = i === chunks.length - 1;
823
- const replyMarkup = isLast && buttons ? {
824
- inline_keyboard: buttons.map(
825
- (row) => row.map((b) => ({ text: b.text, callback_data: b.callback_data }))
826
- )
827
- } : void 0;
828
- await this.apiCall("sendMessage", {
829
- chat_id: chatId,
830
- text: chunks[i],
831
- parse_mode: "Markdown",
832
- ...replyMarkup ? { reply_markup: replyMarkup } : {}
833
- });
834
- }
835
- }
836
- /** Send a plain text message (no markdown parsing) to a specific chat. */
837
- async sendPlain(chatId, text) {
838
- const chunks = splitMessage(text);
839
- for (const chunk of chunks) {
840
- await this.apiCall("sendMessage", {
841
- chat_id: chatId,
842
- text: chunk
843
- });
844
- }
845
- }
846
- /** Send a typing indicator to a specific chat. */
847
- async sendTyping(chatId) {
848
- await this.apiCall("sendChatAction", {
849
- chat_id: chatId,
850
- action: "typing"
851
- });
852
- }
853
- /** Get a direct download URL for a Telegram file. */
854
- async getFileUrl(fileId) {
855
- const result = await this.apiCall("getFile", { file_id: fileId });
856
- if (!result.ok) throw new Error(`Failed to get file: ${JSON.stringify(result)}`);
857
- const filePath = result.result.file_path;
858
- return `${TELEGRAM_API}/file/bot${this.token}/${filePath}`;
859
- }
860
- // ── Private ───────────────────────────────────────────
861
- async getUpdates() {
862
- const result = await this.apiCall("getUpdates", {
863
- offset: this.offset,
864
- timeout: 30,
865
- allowed_updates: ["message", "callback_query", "my_chat_member"]
866
- });
867
- if (!result.ok || !Array.isArray(result.result)) return [];
868
- const updates = result.result;
869
- if (updates.length > 0) {
870
- this.offset = updates[updates.length - 1].update_id + 1;
871
- }
872
- return updates;
873
- }
874
- async handleUpdate(update) {
875
- if (update.message) {
876
- const msg = update.message;
877
- if (msg.from.id !== this.allowedUserId) {
878
- return;
879
- }
880
- if (msg.text && this.onMessage) {
881
- this.onMessage({
882
- text: msg.text,
883
- chatId: msg.chat.id,
884
- chatType: msg.chat.type,
885
- chatTitle: msg.chat.title
886
- });
887
- } else if (msg.voice && this.onVoiceMessage) {
888
- this.onVoiceMessage({
889
- fileId: msg.voice.file_id,
890
- duration: msg.voice.duration,
891
- chatId: msg.chat.id,
892
- chatType: msg.chat.type,
893
- chatTitle: msg.chat.title
894
- });
895
- }
896
- }
897
- if (update.my_chat_member) {
898
- const member = update.my_chat_member;
899
- const status = member.new_chat_member.status;
900
- if ((status === "member" || status === "administrator") && this.onBotAdded) {
901
- this.onBotAdded(member.chat.id, member.chat.title);
902
- } else if ((status === "left" || status === "kicked") && this.onBotRemoved) {
903
- this.onBotRemoved(member.chat.id);
904
- }
905
- }
906
- if (update.callback_query) {
907
- const cb = update.callback_query;
908
- if (cb.from.id !== this.allowedUserId) return;
909
- await this.apiCall("answerCallbackQuery", { callback_query_id: cb.id });
910
- if (cb.data && this.onCallback) {
911
- this.onCallback(cb.data, cb.message.chat.id);
912
- }
913
- }
914
- }
915
- async apiCall(method, body) {
916
- const url = `${TELEGRAM_API}/bot${this.token}/${method}`;
917
- const response = await fetch(url, {
918
- method: "POST",
919
- headers: { "Content-Type": "application/json" },
920
- body: body ? JSON.stringify(body) : void 0
921
- });
922
- if (!response.ok) {
923
- return { ok: false };
924
- }
925
- return response.json();
926
- }
927
- };
928
- function toTelegramMarkdown(text) {
929
- const lines = text.split("\n");
930
- const result = [];
931
- let inCodeBlock = false;
932
- for (const line of lines) {
933
- if (line.trimStart().startsWith("```")) {
934
- inCodeBlock = !inCodeBlock;
935
- result.push(line);
936
- continue;
937
- }
938
- if (inCodeBlock) {
939
- result.push(line);
940
- continue;
941
- }
942
- let transformed = line;
943
- const headingMatch = transformed.match(/^(#{1,6})\s+(.+)$/);
944
- if (headingMatch) {
945
- transformed = `*${headingMatch[2]}*`;
946
- result.push(transformed);
947
- continue;
948
- }
949
- if (/^(-{3,}|_{3,}|\*{3,})$/.test(transformed.trim())) {
950
- result.push("");
951
- continue;
952
- }
953
- transformed = transformed.replace(/\*\*(.+?)\*\*/g, "*$1*");
954
- result.push(transformed);
955
- }
956
- return result.join("\n");
957
- }
958
- function splitMessage(text) {
959
- if (text.length <= MAX_MESSAGE_LENGTH) return [text];
960
- const chunks = [];
961
- let remaining = text;
962
- while (remaining.length > 0) {
963
- if (remaining.length <= MAX_MESSAGE_LENGTH) {
964
- chunks.push(remaining);
965
- break;
966
- }
967
- let splitAt = remaining.lastIndexOf("\n", MAX_MESSAGE_LENGTH);
968
- if (splitAt === -1 || splitAt < MAX_MESSAGE_LENGTH * 0.5) {
969
- splitAt = remaining.lastIndexOf(" ", MAX_MESSAGE_LENGTH);
970
- }
971
- if (splitAt === -1 || splitAt < MAX_MESSAGE_LENGTH * 0.5) {
972
- splitAt = MAX_MESSAGE_LENGTH;
973
- }
974
- chunks.push(remaining.slice(0, splitAt));
975
- remaining = remaining.slice(splitAt).trimStart();
976
- }
977
- return chunks;
978
- }
979
- function sleep(ms) {
980
- return new Promise((r) => setTimeout(r, ms));
981
- }
982
-
983
- // src/serve-mode.ts
984
681
  function getTelegramConfigPath() {
985
682
  return path3.join(getAppPaths().agentDir, "boss", "telegram.json");
986
683
  }
@@ -1001,8 +698,14 @@ async function saveBossTelegramConfig(config) {
1001
698
  }
1002
699
  function formatItemForTelegram(item) {
1003
700
  switch (item.kind) {
701
+ case "banner":
1004
702
  case "user":
1005
- case "tool":
703
+ case "tool_start":
704
+ case "tool_done":
705
+ case "tool_group":
706
+ case "compacting":
707
+ case "compacted":
708
+ case "stopped":
1006
709
  case "worker_event":
1007
710
  return null;
1008
711
  case "assistant": {
@@ -1533,7 +1236,7 @@ function printSetupBanner() {
1533
1236
 
1534
1237
  // src/orchestrator-app.tsx
1535
1238
  init_esm_shims();
1536
- var import_react10 = __toESM(require_react(), 1);
1239
+ var import_react14 = __toESM(require_react(), 1);
1537
1240
 
1538
1241
  // ../ggcoder/dist/ui/components/index.js
1539
1242
  init_esm_shims();
@@ -1543,10 +1246,66 @@ init_esm_shims();
1543
1246
  var import_jsx_runtime3 = __toESM(require_jsx_runtime(), 1);
1544
1247
  var import_react3 = __toESM(require_react(), 1);
1545
1248
 
1546
- // ../ggcoder/dist/ui/components/Overlay.js
1249
+ // ../ggcoder/dist/ui/components/SelectList.js
1547
1250
  init_esm_shims();
1548
1251
  var import_jsx_runtime4 = __toESM(require_jsx_runtime(), 1);
1549
1252
  var import_react4 = __toESM(require_react(), 1);
1253
+ function SelectList({ items, onSelect, onCancel, initialIndex = 0, windowSize }) {
1254
+ const theme = useTheme();
1255
+ const [selectedIndex, setSelectedIndex] = (0, import_react4.useState)(initialIndex);
1256
+ const [filter, setFilter] = (0, import_react4.useState)("");
1257
+ const filtered = (0, import_react4.useMemo)(() => {
1258
+ if (!filter)
1259
+ return items;
1260
+ const lower = filter.toLowerCase();
1261
+ return items.filter((item) => item.label.toLowerCase().includes(lower) || item.value.toLowerCase().includes(lower));
1262
+ }, [items, filter]);
1263
+ use_input_default((input, key) => {
1264
+ const inputWithoutFocusReports = stripTerminalFocusSequences(input);
1265
+ if (!inputWithoutFocusReports && input)
1266
+ return;
1267
+ input = inputWithoutFocusReports;
1268
+ if (key.escape) {
1269
+ onCancel();
1270
+ return;
1271
+ }
1272
+ if (key.return) {
1273
+ if (filtered.length > 0) {
1274
+ onSelect(filtered[selectedIndex].value);
1275
+ }
1276
+ return;
1277
+ }
1278
+ if (key.upArrow) {
1279
+ setSelectedIndex((i) => Math.max(0, i - 1));
1280
+ return;
1281
+ }
1282
+ if (key.downArrow) {
1283
+ setSelectedIndex((i) => Math.min(filtered.length - 1, i + 1));
1284
+ return;
1285
+ }
1286
+ if (key.backspace || key.delete) {
1287
+ setFilter((f) => f.slice(0, -1));
1288
+ setSelectedIndex(0);
1289
+ return;
1290
+ }
1291
+ if (input && !key.ctrl && !key.meta) {
1292
+ setFilter((f) => f + input);
1293
+ setSelectedIndex(0);
1294
+ }
1295
+ });
1296
+ const total = filtered.length;
1297
+ const clampedIndex = Math.min(Math.max(selectedIndex, 0), Math.max(0, total - 1));
1298
+ const useWindow = windowSize !== void 0 && windowSize > 0 && total > windowSize;
1299
+ const start = useWindow ? Math.max(0, Math.min(clampedIndex - Math.floor(windowSize / 2), total - windowSize)) : 0;
1300
+ const end = useWindow ? Math.min(start + windowSize, total) : total;
1301
+ const visible = useWindow ? filtered.slice(start, end) : filtered;
1302
+ const hasAbove = useWindow && start > 0;
1303
+ const hasBelow = useWindow && end < total;
1304
+ 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) => {
1305
+ const index = useWindow ? start + i : i;
1306
+ 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);
1307
+ }), 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" }) })] });
1308
+ }
1550
1309
 
1551
1310
  // ../ggcoder/dist/ui/components/SessionSelector.js
1552
1311
  init_esm_shims();
@@ -1558,14 +1317,19 @@ init_esm_shims();
1558
1317
  var import_jsx_runtime6 = __toESM(require_jsx_runtime(), 1);
1559
1318
  var import_react6 = __toESM(require_react(), 1);
1560
1319
 
1320
+ // src/boss-chat-screen.tsx
1321
+ init_esm_shims();
1322
+ var import_react12 = __toESM(require_react(), 1);
1323
+
1561
1324
  // src/boss-footer.tsx
1562
1325
  init_esm_shims();
1563
1326
  var import_react7 = __toESM(require_react(), 1);
1564
1327
  var import_jsx_runtime7 = __toESM(require_jsx_runtime(), 1);
1565
1328
  var PARTIAL_BLOCKS = [" ", "\u258F", "\u258E", "\u258D", "\u258C", "\u258B", "\u258A", "\u2589", "\u2588"];
1566
1329
  var LIGHT_SHADE = "\u2591";
1330
+ var BAR_WIDTH = 8;
1567
1331
  var SHORT_MODELS = {
1568
- "claude-opus-4-7": "Opus",
1332
+ "claude-opus-4-8": "Opus",
1569
1333
  "claude-sonnet-4-6": "Sonnet",
1570
1334
  "claude-haiku-4-5": "Haiku",
1571
1335
  "claude-haiku-4-5-20251001": "Haiku",
@@ -1577,17 +1341,51 @@ var SHORT_MODELS = {
1577
1341
  function shortModel(model) {
1578
1342
  return SHORT_MODELS[model] ?? model;
1579
1343
  }
1580
- function getContextPercent(model, tokensIn) {
1344
+ function getBossFooterContextPercent(model, tokensIn) {
1581
1345
  const limit = getContextWindow(model);
1582
1346
  if (!limit || tokensIn === 0) return 0;
1583
1347
  return Math.round(tokensIn / limit * 100);
1584
1348
  }
1349
+ function getContextColor(pct, theme) {
1350
+ if (pct >= 80) return theme.error;
1351
+ if (pct >= 50) return theme.warning;
1352
+ return theme.success;
1353
+ }
1354
+ function getThinkingColor(level, theme) {
1355
+ if (!level) return theme.textDim;
1356
+ if (level === "low") return theme.textMuted;
1357
+ if (level === "medium") return theme.accent;
1358
+ if (level === "high") return theme.warning;
1359
+ return COLORS.accent;
1360
+ }
1361
+ function getBossFooterThinkingLabel(level) {
1362
+ return level ? `Thinking ${level}` : "Thinking off";
1363
+ }
1585
1364
  var SHORT_RADIO = {
1586
1365
  "somafm-groove-salad": "Groove Salad",
1587
1366
  "somafm-drone-zone": "Drone Zone",
1588
1367
  "radio-paradise": "Radio Paradise",
1589
1368
  "george-fm": "George FM"
1590
1369
  };
1370
+ function renderContextBar({
1371
+ contextPct,
1372
+ contextColor,
1373
+ dimColor
1374
+ }) {
1375
+ const fillFloat = Math.min(contextPct / 100 * BAR_WIDTH, BAR_WIDTH);
1376
+ const barChars = [];
1377
+ for (let i = 0; i < BAR_WIDTH; i++) {
1378
+ const cellFill = Math.max(0, Math.min(1, fillFloat - i));
1379
+ const eighths = Math.round(cellFill * 8);
1380
+ barChars.push(
1381
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: eighths > 0 ? contextColor : dimColor, children: eighths > 0 ? PARTIAL_BLOCKS[eighths] : LIGHT_SHADE }, i)
1382
+ );
1383
+ }
1384
+ return barChars;
1385
+ }
1386
+ function getBossFooterScopeLabel(scope) {
1387
+ return scope === "all" ? "all projects" : scope;
1388
+ }
1591
1389
  function BossFooter({
1592
1390
  bossModel,
1593
1391
  workerModel,
@@ -1595,149 +1393,231 @@ function BossFooter({
1595
1393
  exitPending,
1596
1394
  bossThinkingLevel,
1597
1395
  updatePending,
1598
- currentRadioStationId
1396
+ currentRadioStationId,
1397
+ scope
1599
1398
  }) {
1600
1399
  const theme = useTheme();
1601
1400
  const { columns } = useTerminalSize();
1602
1401
  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" }) });
1402
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Box_default, { paddingLeft: 1, paddingRight: 1, width: columns, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: theme.warning, children: "Press Ctrl+C again to exit" }) });
1604
1403
  }
1605
- const contextPct = getContextPercent(bossModel, tokensIn);
1606
- const contextColor = contextPct >= 80 ? theme.error : contextPct >= 50 ? theme.warning : theme.success;
1404
+ const contextPct = getBossFooterContextPercent(bossModel, tokensIn);
1405
+ const contextColor = getContextColor(contextPct, theme);
1607
1406
  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
- }
1627
- }
1407
+ const bossName = shortModel(bossModel);
1408
+ const workerName = shortModel(workerModel);
1409
+ const thinkingText = getBossFooterThinkingLabel(bossThinkingLevel);
1628
1410
  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 }),
1411
+ const updateText = updatePending ? "Update ready. Restart GG Boss." : null;
1412
+ const leftText = getBossFooterScopeLabel(scope);
1413
+ const barChars = renderContextBar({
1414
+ contextPct,
1415
+ contextColor,
1416
+ dimColor: theme.textDim
1417
+ });
1418
+ 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);
1419
+ const availableWidth = columns - 2;
1420
+ const fitsOnOneLine = leftText.length + rightLen <= availableWidth;
1421
+ const hideRadio = !!radioName && leftText.length + rightLen > availableWidth + 8;
1422
+ const compactUpdate = !!updateText && leftText.length + rightLen > availableWidth + 12;
1423
+ const rightContent = /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
1424
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { children: barChars }),
1425
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Text, { color: contextColor, children: [
1426
+ " ",
1427
+ contextPct,
1428
+ "%"
1429
+ ] }),
1430
+ sep,
1431
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: theme.primary, bold: true, children: bossName }),
1432
+ sep,
1433
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: theme.textDim, children: "workers " }),
1434
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: COLORS.accent, bold: true, children: workerName }),
1435
+ sep,
1436
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: getThinkingColor(bossThinkingLevel, theme), bold: bossThinkingLevel === "high", children: thinkingText }),
1437
+ radioName && !hideRadio && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
1652
1438
  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 GG Boss." })
1439
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Text, { color: theme.secondary, children: [
1440
+ "\u266A ",
1441
+ radioName
1669
1442
  ] })
1443
+ ] }),
1444
+ updateText && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
1445
+ sep,
1446
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: theme.success, bold: true, wrap: "truncate", children: compactUpdate ? "Update ready" : updateText })
1670
1447
  ] })
1671
1448
  ] });
1449
+ if (fitsOnOneLine) {
1450
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Box_default, { paddingLeft: 1, paddingRight: 1, width: columns, children: [
1451
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Box_default, { flexGrow: 1, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: theme.textDim, wrap: "truncate", children: leftText }) }),
1452
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Box_default, { flexShrink: 0, children: rightContent })
1453
+ ] });
1454
+ }
1455
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Box_default, { flexDirection: "column", paddingLeft: 1, paddingRight: 1, width: columns, children: [
1456
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Box_default, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { color: theme.textDim, wrap: "truncate", children: leftText }) }),
1457
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Box_default, { children: rightContent })
1458
+ ] });
1672
1459
  }
1673
1460
 
1674
- // src/slash-commands.ts
1461
+ // src/boss-model-selector.tsx
1675
1462
  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 gg-boss" }
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;
1463
+ var import_react8 = __toESM(require_react(), 1);
1464
+ var import_jsx_runtime8 = __toESM(require_jsx_runtime(), 1);
1465
+ var MAX_MODELS_TO_SHOW = 8;
1466
+ var PROVIDER_LABEL = {
1467
+ anthropic: "Anthropic",
1468
+ openai: "OpenAI",
1469
+ gemini: "Gemini",
1470
+ glm: "Z.AI",
1471
+ moonshot: "Moonshot",
1472
+ xiaomi: "Xiaomi",
1473
+ minimax: "MiniMax",
1474
+ deepseek: "DeepSeek",
1475
+ openrouter: "OpenRouter"
1476
+ };
1477
+ var ESC = String.fromCharCode(27);
1478
+ var ESC_FOCUS_GAINED = `${ESC}[I`;
1479
+ var ESC_FOCUS_LOST = `${ESC}[O`;
1480
+ var ESC_LESS_FOCUS_GAINED = "[I";
1481
+ var ESC_LESS_FOCUS_LOST = "[O";
1482
+ function stripTerminalFocusSequences2(input) {
1483
+ const withoutEscFocusReports = input.replaceAll(ESC_FOCUS_GAINED, "").replaceAll(ESC_FOCUS_LOST, "");
1484
+ let remaining = withoutEscFocusReports;
1485
+ while (remaining.length > 0) {
1486
+ if (remaining.startsWith(ESC_LESS_FOCUS_GAINED) || remaining.startsWith(ESC_LESS_FOCUS_LOST)) {
1487
+ remaining = remaining.slice(2);
1488
+ continue;
1489
+ }
1490
+ return withoutEscFocusReports;
1700
1491
  }
1701
- return null;
1492
+ return "";
1702
1493
  }
1703
- function buildHelpText() {
1704
- const lines = ["**gg-boss 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");
1494
+ function BossModelSelectList({
1495
+ items,
1496
+ onSelect,
1497
+ onCancel,
1498
+ initialIndex
1499
+ }) {
1500
+ const theme = useTheme();
1501
+ const { columns } = useTerminalSize();
1502
+ const [selectedIndex, setSelectedIndex] = (0, import_react8.useState)(initialIndex);
1503
+ const [filter, setFilter] = (0, import_react8.useState)("");
1504
+ const filtered = (0, import_react8.useMemo)(() => {
1505
+ if (!filter) return items;
1506
+ const lower = filter.toLowerCase();
1507
+ return items.filter(
1508
+ (item) => item.label.toLowerCase().includes(lower) || item.value.toLowerCase().includes(lower) || item.description.toLowerCase().includes(lower)
1509
+ );
1510
+ }, [items, filter]);
1511
+ use_input_default((input, key) => {
1512
+ const inputWithoutFocusReports = stripTerminalFocusSequences2(input);
1513
+ if (!inputWithoutFocusReports && input) return;
1514
+ input = inputWithoutFocusReports;
1515
+ if (key.escape) {
1516
+ onCancel();
1517
+ return;
1518
+ }
1519
+ if (key.return) {
1520
+ const selected = filtered[selectedIndex];
1521
+ if (selected) onSelect(selected.value);
1522
+ return;
1523
+ }
1524
+ if (key.upArrow) {
1525
+ setSelectedIndex((i) => Math.max(0, i - 1));
1526
+ return;
1527
+ }
1528
+ if (key.downArrow) {
1529
+ setSelectedIndex((i) => filtered.length === 0 ? 0 : Math.min(filtered.length - 1, i + 1));
1530
+ return;
1531
+ }
1532
+ if (key.backspace || key.delete) {
1533
+ setFilter((current) => current.slice(0, -1));
1534
+ setSelectedIndex(0);
1535
+ return;
1536
+ }
1537
+ if (input && !key.ctrl && !key.meta) {
1538
+ setFilter((current) => current + input);
1539
+ setSelectedIndex(0);
1540
+ }
1541
+ });
1542
+ const total = filtered.length;
1543
+ const idx = Math.min(Math.max(selectedIndex, 0), Math.max(0, total - 1));
1544
+ 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));
1545
+ const end = Math.min(start + MAX_MODELS_TO_SHOW, total);
1546
+ const visible = filtered.slice(start, end);
1547
+ const width = Math.max(20, columns);
1548
+ const maxLabelLength = Math.max(0, ...filtered.map((item) => item.label.length));
1549
+ const labelColumnWidth = Math.min(maxLabelLength, Math.floor(width * 0.5));
1550
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box_default, { flexDirection: "column", paddingX: 1, width, children: [
1551
+ filter && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { color: theme.textDim, children: [
1552
+ "Filter: ",
1553
+ filter
1554
+ ] }),
1555
+ start > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.text, children: "\u25B2" }),
1556
+ visible.map((item, i) => {
1557
+ const actualIndex = start + i;
1558
+ const isSelected = actualIndex === idx;
1559
+ const textColor = isSelected ? theme.commandColor : theme.textDim;
1560
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1561
+ Box_default,
1562
+ {
1563
+ flexDirection: "row",
1564
+ backgroundColor: isSelected ? theme.border : void 0,
1565
+ children: [
1566
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box_default, { width: labelColumnWidth, flexShrink: 0, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: textColor, children: item.label }) }),
1567
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box_default, { flexGrow: 1, paddingLeft: 3, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: textColor, wrap: "truncate", children: item.description.slice(0, 100) }) })
1568
+ ]
1569
+ },
1570
+ item.value
1571
+ );
1572
+ }),
1573
+ end < total && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.textDim, children: "\u25BC" }),
1574
+ total > MAX_MODELS_TO_SHOW && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { color: theme.textDim, children: [
1575
+ "(",
1576
+ idx + 1,
1577
+ "/",
1578
+ total,
1579
+ ")"
1580
+ ] }),
1581
+ total === 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.textDim, children: "No matches" })
1582
+ ] });
1583
+ }
1584
+ function BossModelSelector({
1585
+ onSelect,
1586
+ onCancel,
1587
+ currentModel,
1588
+ currentProvider
1589
+ }) {
1590
+ const currentValue = `${currentProvider}:${currentModel}`;
1591
+ const items = (0, import_react8.useMemo)(
1592
+ () => MODELS.map((model) => {
1593
+ const value = `${model.provider}:${model.id}`;
1594
+ const isCurrent = value === currentValue;
1595
+ return {
1596
+ label: `${isCurrent ? "* " : " "}${model.name}`,
1597
+ value,
1598
+ description: `${PROVIDER_LABEL[model.provider] ?? model.provider} \xB7 ${model.id}`
1599
+ };
1600
+ }),
1601
+ [currentValue]
1602
+ );
1603
+ const initialIndex = Math.max(
1604
+ 0,
1605
+ items.findIndex((item) => item.value === currentValue)
1606
+ );
1607
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1608
+ BossModelSelectList,
1609
+ {
1610
+ items,
1611
+ onSelect,
1612
+ onCancel,
1613
+ initialIndex
1614
+ }
1615
+ );
1737
1616
  }
1738
1617
 
1739
- // src/tool-formatters.ts
1618
+ // src/boss-tasks-overlay.tsx
1740
1619
  init_esm_shims();
1620
+ var import_react9 = __toESM(require_react(), 1);
1741
1621
 
1742
1622
  // src/colors.ts
1743
1623
  init_esm_shims();
@@ -1770,166 +1650,8 @@ function projectColor(name) {
1770
1650
  return PROJECT_COLORS[stableHash(name) % PROJECT_COLORS.length];
1771
1651
  }
1772
1652
 
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);
1782
- }
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
1653
  // 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);
1654
+ var import_jsx_runtime9 = __toESM(require_jsx_runtime(), 1);
1933
1655
  function statusGlyph(status) {
1934
1656
  switch (status) {
1935
1657
  case "done":
@@ -1952,10 +1674,10 @@ function BossTasksOverlay({
1952
1674
  const theme = useTheme();
1953
1675
  const tasksState = useTasksState();
1954
1676
  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) => {
1677
+ const [selectedIndex, setSelectedIndex] = (0, import_react9.useState)(0);
1678
+ const [status, setStatusMsg] = (0, import_react9.useState)("");
1679
+ const statusTimer = (0, import_react9.useRef)(null);
1680
+ const showStatus = (0, import_react9.useCallback)((msg) => {
1959
1681
  setStatusMsg(msg);
1960
1682
  if (statusTimer.current) clearTimeout(statusTimer.current);
1961
1683
  statusTimer.current = setTimeout(() => setStatusMsg(""), 2500);
@@ -1965,7 +1687,7 @@ function BossTasksOverlay({
1965
1687
  tasks: tasks.filter((t) => t.project === w.name).sort((a, b) => a.createdAt.localeCompare(b.createdAt))
1966
1688
  }));
1967
1689
  const flatTasks = groupedTasks.flatMap((g) => g.tasks);
1968
- (0, import_react8.useEffect)(() => {
1690
+ (0, import_react9.useEffect)(() => {
1969
1691
  if (flatTasks.length === 0) {
1970
1692
  setSelectedIndex(0);
1971
1693
  } else if (selectedIndex >= flatTasks.length) {
@@ -2025,11 +1747,11 @@ function BossTasksOverlay({
2025
1747
  const inProgressCount = tasks.filter((t) => t.status === "in_progress").length;
2026
1748
  const pendingCount = tasks.filter((t) => t.status === "pending").length;
2027
1749
  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)(
1750
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box_default, { flexDirection: "column", marginTop: 1, paddingX: 1, children: [
1751
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box_default, { children: [
1752
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: COLORS.primary, bold: true, children: "Tasks" }),
1753
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.textDim, children: ` \xB7 ${tasks.length} total \xB7 ` }),
1754
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2033
1755
  CountsRow,
2034
1756
  {
2035
1757
  theme,
@@ -2040,28 +1762,28 @@ function BossTasksOverlay({
2040
1762
  }
2041
1763
  )
2042
1764
  ] }),
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: [
1765
+ flatTasks.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { color: theme.textDim, children: [
2044
1766
  " 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"' }),
1767
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.text, children: '"plan some work"' }),
2046
1768
  "."
2047
1769
  ] }) }),
2048
- showingTop && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.textDim, children: ` \u2191 ${startIdx} more above` }),
1770
+ showingTop && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.textDim, children: ` \u2191 ${startIdx} more above` }),
2049
1771
  groupedTasks.map((group, gIdx) => {
2050
1772
  const startInFlat = groupedTasks.slice(0, gIdx).reduce((acc, g) => acc + g.tasks.length, 0);
2051
1773
  const visibleInSection = group.tasks.filter((t) => visibleIdSet.has(t.id));
2052
1774
  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}` })
1775
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box_default, { flexDirection: "column", marginTop: 1, children: [
1776
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { children: [
1777
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: projectColor(group.project), bold: true, children: group.project }),
1778
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.textDim, children: ` \xB7 ${group.tasks.length}` })
2057
1779
  ] }),
2058
1780
  visibleInSection.map((task) => {
2059
1781
  const realIdx = startInFlat + group.tasks.indexOf(task);
2060
1782
  const isSelected = realIdx === selectedIndex;
2061
1783
  const prefix = isSelected ? "\u276F " : " ";
2062
1784
  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: [
1785
+ const color2 = isSelected ? theme.primary : task.status === "done" ? theme.success : task.status === "in_progress" ? theme.warning : task.status === "blocked" ? theme.error : theme.text;
1786
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { color: color2, bold: isSelected, children: [
2065
1787
  prefix,
2066
1788
  "[",
2067
1789
  glyph,
@@ -2071,16 +1793,16 @@ function BossTasksOverlay({
2071
1793
  })
2072
1794
  ] }, group.project);
2073
1795
  }),
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" }),
1796
+ showingBottom && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.textDim, children: ` \u2193 ${flatTasks.length - endIdx} more below` }),
1797
+ status && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.success, children: " " + status }) }),
1798
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { color: theme.textDim, children: [
1799
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.primary, children: "\u2191\u2193" }),
2078
1800
  " move \xB7 (",
2079
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.primary, children: "d" }),
1801
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.primary, children: "d" }),
2080
1802
  ")elete \xB7 (",
2081
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.primary, children: "r" }),
1803
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.primary, children: "r" }),
2082
1804
  ")un pending \xB7 ",
2083
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.primary, children: "ESC" }),
1805
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.primary, children: "ESC" }),
2084
1806
  " close"
2085
1807
  ] }) })
2086
1808
  ] });
@@ -2092,24 +1814,24 @@ function CountsRow({
2092
1814
  pending,
2093
1815
  blocked
2094
1816
  }) {
2095
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { children: [
2096
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { color: theme.success, children: [
1817
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { children: [
1818
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { color: theme.success, children: [
2097
1819
  done,
2098
1820
  " done"
2099
1821
  ] }),
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: [
1822
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.textDim, children: " \xB7 " }),
1823
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { color: theme.warning, children: [
2102
1824
  active,
2103
1825
  " active"
2104
1826
  ] }),
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: [
1827
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.textDim, children: " \xB7 " }),
1828
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { color: theme.text, children: [
2107
1829
  pending,
2108
1830
  " pending"
2109
1831
  ] }),
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: [
1832
+ blocked > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
1833
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.textDim, children: " \xB7 " }),
1834
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { color: theme.error, children: [
2113
1835
  blocked,
2114
1836
  " blocked"
2115
1837
  ] })
@@ -2117,9 +1839,100 @@ function CountsRow({
2117
1839
  ] });
2118
1840
  }
2119
1841
 
1842
+ // src/boss-worker-status-row.tsx
1843
+ init_esm_shims();
1844
+ var import_react10 = __toESM(require_react(), 1);
1845
+ var import_jsx_runtime10 = __toESM(require_jsx_runtime(), 1);
1846
+ var SHIMMER_WIDTH = 3;
1847
+ function formatWorkerElapsed(ms) {
1848
+ const total = Math.floor(ms / 1e3);
1849
+ const m = Math.floor(total / 60);
1850
+ const s = total % 60;
1851
+ return `${m}:${s.toString().padStart(2, "0")}`;
1852
+ }
1853
+ function AnimationActiveSentinel() {
1854
+ useAnimationActive();
1855
+ return null;
1856
+ }
1857
+ function ShimmerName({
1858
+ name,
1859
+ color: color2,
1860
+ tick
1861
+ }) {
1862
+ const cycle = name.length + SHIMMER_WIDTH * 2;
1863
+ const shimmerPos = tick % cycle - SHIMMER_WIDTH;
1864
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { children: name.split("").map((ch, i) => {
1865
+ const isBright = Math.abs(i - shimmerPos) <= SHIMMER_WIDTH;
1866
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: color2, bold: isBright, dimColor: !isBright, children: ch }, i);
1867
+ }) });
1868
+ }
1869
+ function BossWorkerStatusRow({
1870
+ workers,
1871
+ pendingMessages
1872
+ }) {
1873
+ const theme = useTheme();
1874
+ const { columns } = useTerminalSize();
1875
+ const working = workers.filter((w) => w.status === "working");
1876
+ const errored = workers.filter((w) => w.status === "error");
1877
+ const idleCount = workers.length - working.length - errored.length;
1878
+ const anyWorking = working.length > 0;
1879
+ const tick = useAnimationTick();
1880
+ const now = Date.now();
1881
+ if (workers.length === 0) return null;
1882
+ const slots = [];
1883
+ for (const w of working) {
1884
+ const projectHue = projectColor(w.name);
1885
+ const elapsed = w.workStartedAt ? formatWorkerElapsed(now - w.workStartedAt) : null;
1886
+ slots.push(
1887
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react10.default.Fragment, { children: [
1888
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ShimmerName, { name: w.name, color: projectHue, tick }),
1889
+ elapsed && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { color: theme.textDim, children: [
1890
+ " ",
1891
+ elapsed
1892
+ ] })
1893
+ ] }, `w-${w.name}`)
1894
+ );
1895
+ }
1896
+ for (const w of errored) {
1897
+ slots.push(
1898
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react10.default.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { color: theme.error, children: [
1899
+ "\u2717 ",
1900
+ w.name
1901
+ ] }) }, `e-${w.name}`)
1902
+ );
1903
+ }
1904
+ if (idleCount > 0) {
1905
+ slots.push(
1906
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react10.default.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { color: theme.textDim, children: [
1907
+ "\u25CB ",
1908
+ idleCount,
1909
+ " idle"
1910
+ ] }) }, "idle")
1911
+ );
1912
+ }
1913
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { paddingX: 1, width: columns, flexShrink: 1, children: [
1914
+ anyWorking && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(AnimationActiveSentinel, {}),
1915
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { wrap: "truncate", children: [
1916
+ slots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react10.default.Fragment, { children: [
1917
+ i > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: theme.border, children: " \u2502 " }),
1918
+ slot
1919
+ ] }, i)),
1920
+ pendingMessages > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
1921
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: theme.textDim, children: " " }),
1922
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { color: theme.warning, children: [
1923
+ pendingMessages,
1924
+ " message",
1925
+ pendingMessages === 1 ? "" : "s",
1926
+ " queued"
1927
+ ] })
1928
+ ] })
1929
+ ] })
1930
+ ] });
1931
+ }
1932
+
2120
1933
  // src/radio-picker.tsx
2121
1934
  init_esm_shims();
2122
- var import_react9 = __toESM(require_react(), 1);
1935
+ var import_react11 = __toESM(require_react(), 1);
2123
1936
 
2124
1937
  // src/radio.ts
2125
1938
  init_esm_shims();
@@ -2287,7 +2100,7 @@ function buildInstallHint() {
2287
2100
  }
2288
2101
 
2289
2102
  // src/radio-picker.tsx
2290
- var import_jsx_runtime9 = __toESM(require_jsx_runtime(), 1);
2103
+ var import_jsx_runtime11 = __toESM(require_jsx_runtime(), 1);
2291
2104
  function RadioPicker({
2292
2105
  currentStationId: currentStationId2,
2293
2106
  onSelect,
@@ -2309,7 +2122,7 @@ function RadioPicker({
2309
2122
  0,
2310
2123
  items.findIndex((i) => i.value === (currentStationId2 ?? "off"))
2311
2124
  );
2312
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2125
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2313
2126
  SelectList,
2314
2127
  {
2315
2128
  items,
@@ -2321,197 +2134,908 @@ function RadioPicker({
2321
2134
  );
2322
2135
  }
2323
2136
 
2324
- // src/auto-update.ts
2137
+ // src/boss-chat-screen.tsx
2138
+ var import_jsx_runtime12 = __toESM(require_jsx_runtime(), 1);
2139
+ function BossChatScreen({
2140
+ boss,
2141
+ columns,
2142
+ state,
2143
+ overlay,
2144
+ controlsRef = () => {
2145
+ },
2146
+ livePane,
2147
+ theme,
2148
+ statusSlotVisible,
2149
+ activityVisible,
2150
+ stallStatusVisible,
2151
+ doneStatus,
2152
+ elapsedMs,
2153
+ runStartRef,
2154
+ charCountRef,
2155
+ realTokensAccumRef,
2156
+ lastUserMessage,
2157
+ activeToolNames,
2158
+ inputActive,
2159
+ isRunning,
2160
+ onSubmit,
2161
+ onAbort,
2162
+ onTab,
2163
+ onShiftTab,
2164
+ commands,
2165
+ scopeBadge,
2166
+ onCloseOverlay,
2167
+ onModelSelect,
2168
+ currentRadio,
2169
+ onRadioSelect,
2170
+ bossModel,
2171
+ workerModel,
2172
+ updatePending,
2173
+ currentRadioStationId,
2174
+ workers,
2175
+ pendingMessages,
2176
+ formatDuration
2177
+ }) {
2178
+ if (overlay === "tasks") {
2179
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ChatLayout, { columns, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(BossTasksOverlay, { boss, workers, onClose: onCloseOverlay }) });
2180
+ }
2181
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(ChatLayout, { columns, children: [
2182
+ livePane,
2183
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(ChatControls, { controlsRef, children: [
2184
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2185
+ ChatInputStack,
2186
+ {
2187
+ columns,
2188
+ theme,
2189
+ statusSlotVisible,
2190
+ activityVisible,
2191
+ stallStatusVisible,
2192
+ liveToolFeed: [],
2193
+ doneStatus,
2194
+ activityPhase: state.activityPhase,
2195
+ elapsedMs,
2196
+ runStartRef,
2197
+ thinkingMs: state.streaming?.thinkingMs ?? 0,
2198
+ isThinking: state.activityPhase === "thinking",
2199
+ thinkingLevel: state.bossThinkingLevel,
2200
+ tokenEstimate: state.bossInputTokens,
2201
+ charCountRef,
2202
+ realTokensAccumRef,
2203
+ lastUserMessage,
2204
+ activeToolNames,
2205
+ retryInfo: state.retryInfo,
2206
+ planDone: 0,
2207
+ planTotal: 0,
2208
+ renderMarkdown: true,
2209
+ formatDuration
2210
+ }
2211
+ ),
2212
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2213
+ InputArea,
2214
+ {
2215
+ onSubmit,
2216
+ onAbort,
2217
+ disabled: isRunning,
2218
+ isActive: inputActive,
2219
+ cwd: process.cwd(),
2220
+ commands,
2221
+ scopeBadge,
2222
+ disableMouseTracking: true,
2223
+ onTab,
2224
+ onShiftTab
2225
+ }
2226
+ ),
2227
+ overlay === "model-boss" || overlay === "model-workers" ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2228
+ BossModelSelector,
2229
+ {
2230
+ onSelect: onModelSelect,
2231
+ onCancel: onCloseOverlay,
2232
+ currentModel: overlay === "model-boss" ? state.bossModel : state.workerModel,
2233
+ currentProvider: overlay === "model-boss" ? state.bossProvider : state.workerProvider
2234
+ }
2235
+ ) : overlay === "radio" ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2236
+ RadioPicker,
2237
+ {
2238
+ currentStationId: currentRadio,
2239
+ onCancel: onCloseOverlay,
2240
+ onSelect: onRadioSelect
2241
+ }
2242
+ ) : /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
2243
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2244
+ BossFooter,
2245
+ {
2246
+ bossModel,
2247
+ workerModel,
2248
+ tokensIn: state.bossInputTokens,
2249
+ exitPending: state.exitPending,
2250
+ bossThinkingLevel: state.bossThinkingLevel,
2251
+ updatePending,
2252
+ currentRadioStationId,
2253
+ scope: state.scope
2254
+ }
2255
+ ),
2256
+ !state.exitPending && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(BossWorkerStatusRow, { workers, pendingMessages })
2257
+ ] })
2258
+ ] })
2259
+ ] });
2260
+ }
2261
+
2262
+ // src/slash-commands.ts
2325
2263
  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 = "@kenkaiiii/gg-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(), ".gg", "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
- }
2264
+ var BOSS_SLASH_COMMANDS = [
2265
+ { name: "help", aliases: ["?"], description: "Show available commands" },
2266
+ {
2267
+ name: "model-boss",
2268
+ aliases: ["m", "model", "models"],
2269
+ description: "Switch the orchestrator's model"
2270
+ },
2271
+ { name: "model-workers", aliases: [], description: "Switch every worker's model" },
2272
+ { name: "compact", aliases: [], description: "Compact the boss's context now" },
2273
+ { name: "clear", aliases: [], description: "Clear chat history and terminal" },
2274
+ { name: "radio", aliases: [], description: "Stream a free internet radio station" },
2275
+ { name: "quit", aliases: ["q", "exit"], description: "Exit gg-boss" }
2276
+ ];
2277
+ function isSlashCommand(value) {
2278
+ return value.startsWith("/") && !value.startsWith("//");
2344
2279
  }
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 {
2351
- }
2280
+ function parseSlash(value) {
2281
+ if (!isSlashCommand(value)) return null;
2282
+ const rest = value.slice(1).trim();
2283
+ if (!rest) return null;
2284
+ const space = rest.indexOf(" ");
2285
+ if (space === -1) return { name: rest.toLowerCase(), args: "" };
2286
+ return { name: rest.slice(0, space).toLowerCase(), args: rest.slice(space + 1).trim() };
2352
2287
  }
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;
2288
+ function canonicalName(name) {
2289
+ for (const cmd of BOSS_SLASH_COMMANDS) {
2290
+ if (cmd.name === name) return cmd.name;
2291
+ if (cmd.aliases.includes(name)) return cmd.name;
2359
2292
  }
2360
- return 0;
2293
+ return null;
2361
2294
  }
2362
- function detectInstallInfo() {
2363
- const scriptPath = (process.argv[1] ?? "").replace(/\\/g, "/");
2364
- if (scriptPath.includes("/_npx/")) {
2365
- return { packageManager: "unknown" /* UNKNOWN */, updateCommand: null };
2295
+ function buildHelpText() {
2296
+ const lines = ["**gg-boss commands**", ""];
2297
+ for (const cmd of BOSS_SLASH_COMMANDS) {
2298
+ const aliases = cmd.aliases.length > 0 ? ` (${cmd.aliases.map((a) => "/" + a).join(", ")})` : "";
2299
+ lines.push(`- \`/${cmd.name}\`${aliases} \u2014 ${cmd.description}`);
2366
2300
  }
2367
- if (scriptPath.includes("/.pnpm") || scriptPath.includes("/pnpm/global")) {
2368
- return {
2369
- packageManager: "pnpm" /* PNPM */,
2370
- updateCommand: `pnpm add -g ${PACKAGE_NAME}@latest`
2371
- };
2301
+ lines.push("");
2302
+ lines.push("**Global keybindings**");
2303
+ lines.push("- `Ctrl+T` \u2014 open the Tasks pane");
2304
+ lines.push("- `Tab` \u2014 switch project scope (All / per-project pill in the input)");
2305
+ lines.push("- `Shift+Tab` \u2014 cycle the boss's thinking level, then off");
2306
+ lines.push("- `Esc` \u2014 interrupt the boss while it's running");
2307
+ lines.push("- `Ctrl+C` (twice) \u2014 exit");
2308
+ lines.push("");
2309
+ lines.push("**Inside the Tasks pane (Ctrl+T)**");
2310
+ lines.push("- `\u2191` / `\u2193` (or `k` / `j`) \u2014 navigate tasks");
2311
+ lines.push("- `r` \u2014 run all pending and blocked tasks across idle workers");
2312
+ lines.push("- `d` \u2014 delete the selected task");
2313
+ lines.push("- `Esc` \u2014 close the Tasks pane");
2314
+ lines.push("");
2315
+ lines.push("**Inside model pickers (`/model`, `/models`, `/model-boss`, `/model-workers`)**");
2316
+ lines.push("- `\u2191` / `\u2193` \u2014 navigate models");
2317
+ lines.push("- `Enter` \u2014 select");
2318
+ lines.push("- `Esc` \u2014 cancel");
2319
+ lines.push("");
2320
+ lines.push("**Radio** (`/radio`)");
2321
+ lines.push("- Pick a station to stream while you work, or select `Off` to stop.");
2322
+ lines.push("- Requires `mpv` (recommended), `ffplay`, `mpg123`, or `vlc/cvlc` installed.");
2323
+ lines.push("");
2324
+ lines.push("**Input area**");
2325
+ lines.push("- `\u2191` / `\u2193` \u2014 recall previous prompts (when input is empty)");
2326
+ lines.push("- `Enter` \u2014 send \xB7 `Shift+Enter` \u2014 newline");
2327
+ lines.push("- `/` \u2014 open the slash-command menu (Tab / arrows to pick, Enter to insert)");
2328
+ return lines.join("\n");
2329
+ }
2330
+
2331
+ // src/boss-transcript-rows.tsx
2332
+ init_esm_shims();
2333
+ var import_react13 = __toESM(require_react(), 1);
2334
+
2335
+ // src/boss-spacing.ts
2336
+ init_esm_shims();
2337
+ var BOSS_SPACING_KINDS = /* @__PURE__ */ new Set([
2338
+ "user",
2339
+ "assistant",
2340
+ "tool_start",
2341
+ "tool_done",
2342
+ "tool_group",
2343
+ "worker_event",
2344
+ "worker_error",
2345
+ "task_dispatch",
2346
+ "info",
2347
+ "update_notice",
2348
+ "compacting",
2349
+ "compacted",
2350
+ "stopped"
2351
+ ]);
2352
+ var BOSS_COMPACT_BOUNDARIES = /* @__PURE__ */ new Set([
2353
+ "user\u2192assistant",
2354
+ "assistant\u2192user",
2355
+ "user\u2192queued"
2356
+ ]);
2357
+
2358
+ // src/tool-formatters.ts
2359
+ init_esm_shims();
2360
+ function truncate2(s, max) {
2361
+ if (max <= 1) return "\u2026";
2362
+ return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
2363
+ }
2364
+ function promptWorkerDetailLen(project) {
2365
+ const cols = process.stdout.columns ?? 80;
2366
+ const fixed = 2 + 13 + 1 + project.length + 3 + 1 + 1 + 11 + 6;
2367
+ return Math.max(20, cols - fixed);
2368
+ }
2369
+ var bossToolFormatters = {
2370
+ formatLabel(name) {
2371
+ switch (name) {
2372
+ case "list_workers":
2373
+ return "List Workers";
2374
+ case "get_worker_status":
2375
+ return "Worker Status";
2376
+ case "prompt_worker":
2377
+ return "Prompt Worker";
2378
+ case "get_worker_summary":
2379
+ return "Worker Summary";
2380
+ default:
2381
+ return void 0;
2382
+ }
2383
+ },
2384
+ formatDetail(name, args) {
2385
+ switch (name) {
2386
+ case "list_workers":
2387
+ return "";
2388
+ case "get_worker_status":
2389
+ case "get_worker_summary":
2390
+ return truncate2(String(args.project ?? ""), 40);
2391
+ case "prompt_worker": {
2392
+ const project = String(args.project ?? "");
2393
+ const message = String(args.message ?? "").replace(/\s+/g, " ");
2394
+ const fresh = args.fresh === true;
2395
+ const maxMsg = promptWorkerDetailLen(project) - (fresh ? 8 : 0);
2396
+ const truncMsg = truncate2(message, Math.max(15, maxMsg));
2397
+ const head = fresh ? "fresh \xB7 " : "";
2398
+ return project ? `${head}${project} \xB7 ${truncMsg}` : `${head}${truncMsg}`;
2399
+ }
2400
+ default:
2401
+ return void 0;
2402
+ }
2403
+ },
2404
+ formatInline(name, result, isError) {
2405
+ if (isError) return void 0;
2406
+ switch (name) {
2407
+ case "list_workers": {
2408
+ const lines = result.split("\n").filter((l) => l.startsWith("-"));
2409
+ return `${lines.length} worker${lines.length === 1 ? "" : "s"}`;
2410
+ }
2411
+ case "prompt_worker": {
2412
+ if (result.includes("currently working")) {
2413
+ return { text: "busy \u2014 skipped", color: "#fbbf24" };
2414
+ }
2415
+ if (result.includes("Unknown project")) {
2416
+ return { text: "unknown project", color: "#f87171" };
2417
+ }
2418
+ const project = String(result.match(/"([^"]+)"/)?.[1] ?? "");
2419
+ const color2 = project ? projectColor(project) : "#e11d48";
2420
+ return { text: "dispatched", color: color2 };
2421
+ }
2422
+ case "get_worker_status": {
2423
+ const parts = result.split(":");
2424
+ if (parts.length < 2) return void 0;
2425
+ const status = parts.slice(1).join(":").trim();
2426
+ const project = parts[0].trim();
2427
+ return { text: status, color: projectColor(project) };
2428
+ }
2429
+ case "get_worker_summary": {
2430
+ const turnMatch = result.match(/Turn:\s*(\d+)/);
2431
+ const projectMatch = result.match(/Project:\s*(.+)/);
2432
+ const toolsMatch = result.match(/Tools used:\s*(.+)/);
2433
+ const tools = toolsMatch ? toolsMatch[1] : "";
2434
+ const toolCount = tools && tools !== "(no tools used)" ? tools.split(",").length : 0;
2435
+ const turn = turnMatch ? `turn ${turnMatch[1]}` : void 0;
2436
+ const tCount = toolCount > 0 ? `${toolCount} tool${toolCount === 1 ? "" : "s"}` : void 0;
2437
+ const summary = [turn, tCount].filter(Boolean).join(" \xB7 ");
2438
+ if (!summary) return void 0;
2439
+ const project = projectMatch ? projectMatch[1].trim() : "";
2440
+ return project ? { text: summary, color: projectColor(project) } : { text: summary, color: "#9ca3af" };
2441
+ }
2442
+ default:
2443
+ return void 0;
2444
+ }
2372
2445
  }
2373
- if (scriptPath.includes("/.yarn/") || scriptPath.includes("/yarn/global")) {
2374
- return {
2375
- packageManager: "yarn" /* YARN */,
2376
- updateCommand: `yarn global add ${PACKAGE_NAME}@latest`
2377
- };
2446
+ };
2447
+
2448
+ // src/boss-transcript-rows.tsx
2449
+ var import_jsx_runtime13 = __toESM(require_jsx_runtime(), 1);
2450
+ function getBossTranscriptMarginTop({
2451
+ row,
2452
+ previousRow
2453
+ }) {
2454
+ if (row.kind === "banner" || previousRow?.kind === "banner") return 0;
2455
+ if (!BOSS_SPACING_KINDS.has(row.kind)) return 0;
2456
+ return getTranscriptItemMarginTop({
2457
+ item: row,
2458
+ previousLiveItem: previousRow,
2459
+ spacingKinds: BOSS_SPACING_KINDS,
2460
+ compactBoundaries: BOSS_COMPACT_BOUNDARIES
2461
+ });
2462
+ }
2463
+ function BossTranscriptRow({
2464
+ row,
2465
+ previousRow
2466
+ }) {
2467
+ if (row.kind === "banner") {
2468
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Box_default, { paddingX: 1, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(BossBanner, { subtitle: "Orchestrator", showShortcuts: true }) });
2469
+ }
2470
+ const marginTop = getBossTranscriptMarginTop({ row, previousRow });
2471
+ const renderWithSpacing = (node) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(TranscriptItemFrame, { marginTop, children: node });
2472
+ if (row.kind === "user") return renderWithSpacing(/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(UserMessage, { text: row.text }));
2473
+ if (row.kind === "assistant") return renderWithSpacing(/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(AssistantRow, { item: row }));
2474
+ if (row.kind === "tool_start") return renderWithSpacing(/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ToolStartHistoryRow, { item: row }));
2475
+ if (row.kind === "tool_done") return renderWithSpacing(/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ToolHistoryRow, { item: row }));
2476
+ if (row.kind === "tool_group") return renderWithSpacing(/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ToolGroupRow, { item: row }));
2477
+ if (row.kind === "worker_event") return renderWithSpacing(/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(WorkerEventRow, { item: row }));
2478
+ if (row.kind === "worker_error") return renderWithSpacing(/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(WorkerErrorRow, { item: row }));
2479
+ if (row.kind === "info") {
2480
+ return renderWithSpacing(/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(InfoRow, { text: row.text, level: row.level ?? "info" }));
2481
+ }
2482
+ if (row.kind === "task_dispatch") return renderWithSpacing(/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(TaskDispatchRow, { tasks: row.tasks }));
2483
+ if (row.kind === "update_notice") return renderWithSpacing(/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(UpdateNoticeRow, { text: row.text }));
2484
+ if (row.kind === "compacting") return renderWithSpacing(/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(CompactionSpinner, { staticDisplay: true }));
2485
+ if (row.kind === "compacted") {
2486
+ return renderWithSpacing(
2487
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2488
+ CompactionDone,
2489
+ {
2490
+ originalCount: row.originalCount,
2491
+ newCount: row.newCount,
2492
+ tokensBefore: row.tokensBefore,
2493
+ tokensAfter: row.tokensAfter
2494
+ }
2495
+ )
2496
+ );
2378
2497
  }
2379
- return {
2380
- packageManager: "npm" /* NPM */,
2381
- updateCommand: `npm install -g ${PACKAGE_NAME}@latest`
2382
- };
2498
+ if (row.kind === "stopped") return renderWithSpacing(/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(InfoRow, { text: row.text, level: "warning" }));
2499
+ return null;
2383
2500
  }
2384
- async function fetchLatestVersion() {
2385
- try {
2386
- const controller = new AbortController();
2387
- const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
2388
- const response = await fetch(REGISTRY_URL, { signal: controller.signal });
2389
- clearTimeout(timeout);
2390
- const data = await response.json();
2391
- const version = data.version?.trim();
2392
- return version && /^\d+\.\d+\.\d+/.test(version) ? version : null;
2393
- } catch {
2394
- return null;
2501
+ function UpdateNoticeRow({ text }) {
2502
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Box_default, { flexShrink: 1, borderStyle: "round", borderColor: COLORS.accent, paddingX: 1, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Text, { wrap: "wrap", children: [
2503
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { color: COLORS.accent, bold: true, children: "\u2728 " }),
2504
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { color: COLORS.primary, bold: true, children: text })
2505
+ ] }) });
2506
+ }
2507
+ function TaskDispatchRow({
2508
+ tasks
2509
+ }) {
2510
+ const theme = useTheme();
2511
+ const count = tasks.length;
2512
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Box_default, { flexDirection: "column", paddingX: 1, children: [
2513
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Text, { children: [
2514
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { color: COLORS.primary, bold: true, children: "\u23FA " }),
2515
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Text, { color: theme.text, bold: true, children: [
2516
+ "Running ",
2517
+ count,
2518
+ " task",
2519
+ count === 1 ? "" : "s",
2520
+ ":"
2521
+ ] })
2522
+ ] }),
2523
+ tasks.map((t, i) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Text, { children: [
2524
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { color: theme.textDim, children: " \u2022 " }),
2525
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { color: projectColor(t.project), bold: true, children: t.project }),
2526
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { color: theme.textDim, children: ": " }),
2527
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { color: theme.text, children: t.title })
2528
+ ] }, `${t.project}-${i}`))
2529
+ ] });
2530
+ }
2531
+ var SHORTCUT_PATTERNS = [
2532
+ // Modifier+Key combos: Ctrl+T, Shift+Tab, Cmd+K, Ctrl+Shift+P, Ctrl+C
2533
+ /\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,
2534
+ // Bare named keys (only when surrounded by clear key context)
2535
+ /\b(?:Ctrl-[A-Z]|F[1-9]|F1[0-2])\b/g
2536
+ ];
2537
+ function highlightShortcuts(text) {
2538
+ if (!text) return text;
2539
+ const SENTINEL = "\uE000";
2540
+ const masks = [];
2541
+ let masked = text.replace(/```[\s\S]*?```|`[^`]+`/g, (m) => {
2542
+ const idx = masks.push(m) - 1;
2543
+ return `${SENTINEL}${idx}${SENTINEL}`;
2544
+ });
2545
+ for (const re of SHORTCUT_PATTERNS) {
2546
+ masked = masked.replace(re, (m) => `\`${m}\``);
2395
2547
  }
2548
+ return masked.replace(
2549
+ new RegExp(`${SENTINEL}(\\d+)${SENTINEL}`, "g"),
2550
+ (_, i) => masks[Number(i)]
2551
+ );
2396
2552
  }
2397
- function performUpdateInBackground(command) {
2398
- try {
2399
- const parts = command.split(" ");
2400
- const child = spawn2(parts[0], parts.slice(1), {
2401
- detached: true,
2402
- stdio: "ignore",
2403
- env: { ...process.env, npm_config_loglevel: "silent" }
2404
- });
2405
- child.unref();
2406
- } catch {
2553
+ function AssistantRow({ item }) {
2554
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(AssistantMessage, { text: highlightShortcuts(item.text), renderMarkdown: true });
2555
+ }
2556
+ function ToolStartHistoryRow({
2557
+ item
2558
+ }) {
2559
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2560
+ ToolExecution,
2561
+ {
2562
+ status: "running",
2563
+ name: item.name,
2564
+ args: item.args,
2565
+ formatters: bossToolFormatters
2566
+ }
2567
+ );
2568
+ }
2569
+ function ToolGroupRow({ item }) {
2570
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ToolGroupExecution, { tools: item.tools, summaryRenderers: bossToolGroupRenderers });
2571
+ }
2572
+ function ToolHistoryRow({ item }) {
2573
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2574
+ ToolExecution,
2575
+ {
2576
+ status: "done",
2577
+ name: item.name,
2578
+ args: item.args,
2579
+ result: item.result,
2580
+ isError: item.isError,
2581
+ details: item.details,
2582
+ formatters: bossToolFormatters
2583
+ }
2584
+ );
2585
+ }
2586
+ function parseStatusGrade(text) {
2587
+ const matches = [
2588
+ ...text.matchAll(/(?:^|\b)Status:\s*(DONE|UNVERIFIED|PARTIAL|BLOCKED|INFO)\b/gim)
2589
+ ];
2590
+ const last = matches[matches.length - 1];
2591
+ if (!last) return null;
2592
+ return last[1].toUpperCase();
2593
+ }
2594
+ function statusGradeColor(grade, theme) {
2595
+ switch (grade) {
2596
+ case "DONE":
2597
+ return theme.success;
2598
+ case "UNVERIFIED":
2599
+ case "PARTIAL":
2600
+ return theme.warning;
2601
+ case "BLOCKED":
2602
+ return theme.error;
2603
+ case "INFO":
2604
+ return theme.textDim;
2605
+ default:
2606
+ return theme.textDim;
2407
2607
  }
2408
2608
  }
2409
- function checkAndAutoUpdate(currentVersion) {
2410
- try {
2411
- const state = readState();
2412
- let message = null;
2413
- if (state?.updatePending && state.latestVersion) {
2414
- if (compareVersions(state.latestVersion, currentVersion) > 0) {
2415
- const info = detectInstallInfo();
2416
- if (info.updateCommand) {
2417
- performUpdateInBackground(info.updateCommand);
2418
- message = `Ken just shipped ${state.latestVersion}! Installing in the background \u2014 takes effect next launch.`;
2419
- writeState({
2420
- ...state,
2421
- lastCheckedAt: Date.now(),
2422
- updatePending: false,
2423
- lastUpdateAttempt: Date.now()
2424
- });
2609
+ function WorkerEventRow({ item }) {
2610
+ const theme = useTheme();
2611
+ const failedCount = item.toolsUsed.filter((t) => !t.ok).length;
2612
+ const grade = parseStatusGrade(item.finalText);
2613
+ const loaderStatus = grade === "BLOCKED" || failedCount > 0 ? "error" : grade === "UNVERIFIED" || grade === "PARTIAL" ? "queued" : "done";
2614
+ const headerColor = loaderStatus === "error" ? theme.toolError : projectColor(item.project);
2615
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Box_default, { flexDirection: "row", children: [
2616
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ToolUseLoader, { status: loaderStatus }),
2617
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Box_default, { flexGrow: 1, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Text, { wrap: "wrap", children: [
2618
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { color: headerColor, bold: true, children: item.project }),
2619
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { color: theme.text, children: ` turn ${item.turnIndex}` }),
2620
+ grade && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
2621
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { color: theme.textDim, children: " \xB7 " }),
2622
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { color: statusGradeColor(grade, theme), bold: true, children: grade })
2623
+ ] })
2624
+ ] }) })
2625
+ ] });
2626
+ }
2627
+ function WorkerErrorRow({ item }) {
2628
+ const theme = useTheme();
2629
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Box_default, { flexDirection: "column", children: [
2630
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Box_default, { flexDirection: "row", children: [
2631
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ToolUseLoader, { status: "error" }),
2632
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Box_default, { flexGrow: 1, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Text, { wrap: "wrap", children: [
2633
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { color: theme.toolError, bold: true, children: item.project }),
2634
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { color: theme.textDim, children: " worker error" })
2635
+ ] }) })
2636
+ ] }),
2637
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(MessageResponse, { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { color: theme.error, wrap: "wrap", children: item.message }) })
2638
+ ] });
2639
+ }
2640
+ function InfoRow({
2641
+ text,
2642
+ level
2643
+ }) {
2644
+ if (level === "info") return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(AssistantMessage, { text });
2645
+ const theme = useTheme();
2646
+ const color2 = level === "error" ? theme.error : theme.warning;
2647
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Box_default, { flexDirection: "row", children: [
2648
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ToolUseLoader, { status: level === "error" ? "error" : "queued" }),
2649
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Box_default, { flexGrow: 1, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Text, { color: color2, wrap: "wrap", children: text }) })
2650
+ ] });
2651
+ }
2652
+ function BossStreamingTurnView({
2653
+ turn,
2654
+ isRunning,
2655
+ liveItems = [],
2656
+ lastPendingHistoryItem,
2657
+ lastHistoryItem,
2658
+ availableTerminalHeight = 20
2659
+ }) {
2660
+ const visibleLiveItems = liveItems.filter(
2661
+ (item) => item.kind === "user" || item.kind === "tool_start" || item.kind === "tool_done" || item.kind === "tool_group" || item.kind === "compacting" || item.kind === "compacted"
2662
+ );
2663
+ const lastLiveItem = visibleLiveItems[visibleLiveItems.length - 1];
2664
+ const visibleStreamingText = turn?.text ?? "";
2665
+ const previousTranscriptItem = lastPendingHistoryItem ?? lastHistoryItem;
2666
+ const isAwaitingAssistantAfterUser = isRunning && visibleStreamingText.trim().length === 0 && (lastLiveItem?.kind === "user" || !lastLiveItem && previousTranscriptItem?.kind === "user");
2667
+ const shouldReserveStreamingSpacing = isRunning && (visibleStreamingText.trim().length > 0 || visibleLiveItems.some((item) => BOSS_SPACING_KINDS.has(item.kind)) || isAwaitingAssistantAfterUser);
2668
+ const assistantMarginTop = shouldTopSpaceStreamingAssistant({
2669
+ visibleStreamingText,
2670
+ lastLiveItem,
2671
+ lastPendingHistoryItem,
2672
+ lastHistoryItem,
2673
+ spacingKinds: BOSS_SPACING_KINDS,
2674
+ compactBoundaries: BOSS_COMPACT_BOUNDARIES
2675
+ }) ? 1 : 0;
2676
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2677
+ ChatLivePane,
2678
+ {
2679
+ liveItems: visibleLiveItems,
2680
+ renderItem: (_item, index) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2681
+ BossTranscriptRow,
2682
+ {
2683
+ row: visibleLiveItems[index],
2684
+ previousRow: index > 0 ? visibleLiveItems[index - 1] : previousTranscriptItem
2425
2685
  }
2426
- } else {
2427
- writeState({ ...state, updatePending: false });
2686
+ ),
2687
+ isRunning,
2688
+ visibleStreamingText,
2689
+ streamingThinking: "",
2690
+ thinkingMs: turn?.thinkingMs ?? 0,
2691
+ reserveStreamingSpacing: shouldReserveStreamingSpacing,
2692
+ renderMarkdown: true,
2693
+ measuredLiveAreaRows: availableTerminalHeight,
2694
+ assistantMarginTop,
2695
+ streamingContinuation: false
2696
+ }
2697
+ );
2698
+ }
2699
+
2700
+ // src/boss-terminal-history.tsx
2701
+ init_esm_shims();
2702
+ function createBossTerminalHistoryPrinter({
2703
+ stream = process.stdout
2704
+ } = {}) {
2705
+ const printed = /* @__PURE__ */ new Set();
2706
+ let previousPrintedKind = null;
2707
+ return {
2708
+ print(items, context, options) {
2709
+ const writeOutput = options?.write ?? ((data) => void stream.write(data));
2710
+ for (const item of items) {
2711
+ if (!options?.force && printed.has(item.id)) continue;
2712
+ const output = serializeBossItemToTerminalHistory(item, context);
2713
+ const formatted = formatHistoryWrite(output, {
2714
+ leadingSeparator: item.kind === "banner" ? false : shouldSeparateTranscriptItemKinds({
2715
+ previousKind: previousPrintedKind ?? void 0,
2716
+ currentKind: item.kind,
2717
+ spacingKinds: BOSS_SPACING_KINDS,
2718
+ compactBoundaries: BOSS_COMPACT_BOUNDARIES
2719
+ }),
2720
+ trailingBlankLine: item.kind === "banner",
2721
+ trailingNewlines: item.kind === "user" ? 1 : void 0
2722
+ });
2723
+ if (formatted.length === 0) continue;
2724
+ printed.add(item.id);
2725
+ writeOutput(formatted);
2726
+ previousPrintedKind = item.kind;
2428
2727
  }
2728
+ },
2729
+ clear() {
2730
+ printed.clear();
2731
+ previousPrintedKind = null;
2732
+ },
2733
+ resetPrinted() {
2734
+ printed.clear();
2735
+ previousPrintedKind = null;
2736
+ },
2737
+ get printedIds() {
2738
+ return printed;
2429
2739
  }
2430
- const shouldCheck = !state || Date.now() - state.lastCheckedAt > CHECK_INTERVAL_MS;
2431
- if (shouldCheck) scheduleBackgroundCheck(currentVersion);
2432
- return message;
2433
- } catch {
2434
- return null;
2435
- }
2740
+ };
2436
2741
  }
2437
- function getPendingUpdate(currentVersion) {
2438
- try {
2439
- const state = readState();
2440
- if (!state?.latestVersion) return null;
2441
- if (compareVersions(state.latestVersion, currentVersion) <= 0) return null;
2442
- return { latestVersion: state.latestVersion };
2443
- } catch {
2444
- return null;
2445
- }
2742
+ function serializeBossItemToTerminalHistory(item, context) {
2743
+ switch (item.kind) {
2744
+ case "banner":
2745
+ return renderBanner(context);
2746
+ case "worker_event":
2747
+ return renderWorkerEvent(item, context);
2748
+ case "worker_error":
2749
+ return renderWorkerError(item, context);
2750
+ case "task_dispatch":
2751
+ return renderTaskDispatch(item, context);
2752
+ case "tool_group":
2753
+ return renderToolGroup(item, context);
2754
+ case "update_notice":
2755
+ return renderUpdateNotice(item, context);
2756
+ default:
2757
+ return serializeCompletedItemToTerminalHistory(toGGCoderCompletedItem(item), context);
2758
+ }
2759
+ }
2760
+ function renderBanner(context) {
2761
+ const logo = LOGO_LINES.map((lineText) => gradientLine(lineText, GRADIENT));
2762
+ const shortcuts = `${color(COLORS.primary, "^T")} ${dim(context, "tasks ")}${color(
2763
+ COLORS.primary,
2764
+ "Tab"
2765
+ )} ${dim(context, "scope ")}${color(COLORS.primary, "\u21E7Tab")} ${dim(
2766
+ context,
2767
+ "thinking "
2768
+ )}${color(COLORS.primary, "ESC")} ${dim(context, "interrupt")}`;
2769
+ return [
2770
+ "",
2771
+ `${logo[0]}${LOGO_GAP}${color(COLORS.primary, BRAND, true)}${dim(
2772
+ context,
2773
+ ` v${VERSION} \xB7 By `
2774
+ )}${color(COLORS.text, AUTHOR, true)}`,
2775
+ `${logo[1]}${LOGO_GAP}${color(COLORS.accent, "Orchestrator")}`,
2776
+ `${logo[2]}${LOGO_GAP}${shortcuts}`,
2777
+ ""
2778
+ ].join("\n");
2446
2779
  }
2447
- function scheduleBackgroundCheck(currentVersion) {
2448
- fetchLatestVersion().then((latestVersion) => {
2449
- const newState = {
2450
- lastCheckedAt: Date.now(),
2451
- latestVersion: latestVersion ?? void 0,
2452
- updatePending: false
2453
- };
2454
- if (latestVersion && compareVersions(latestVersion, currentVersion) > 0) {
2455
- newState.updatePending = true;
2456
- }
2457
- writeState(newState);
2458
- }).catch(() => {
2780
+ function renderUpdateNotice(item, context) {
2781
+ return renderRoundNoticeBox(
2782
+ [`${color(COLORS.accent, "\u2728 ", true)}${color(COLORS.primary, item.text, true)}`],
2783
+ context,
2784
+ COLORS.accent
2785
+ );
2786
+ }
2787
+ function renderToolGroup(item, context) {
2788
+ const tools = item.tools;
2789
+ const allDone = tools.every((tool) => tool.status === "done");
2790
+ const hasError = tools.some((tool) => tool.isError);
2791
+ const status = allDone ? hasError ? "error" : "done" : "running";
2792
+ const label = buildToolGroupSummary(tools, allDone, bossToolGroupRenderers).map((seg) => {
2793
+ const hex = seg.tone ? toolTonePalette(context.theme, seg.tone).primary : context.theme.toolName;
2794
+ return color(hex, seg.text, seg.bold);
2795
+ }).join("");
2796
+ return toolStatusHeader({ status, label, context, labelAlreadyStyled: true });
2797
+ }
2798
+ function renderWorkerEvent(item, context) {
2799
+ const theme = context.theme;
2800
+ const failedCount = item.toolsUsed.filter((tool) => !tool.ok).length;
2801
+ const grade = parseStatusGrade(item.finalText);
2802
+ const isError = grade === "BLOCKED" || failedCount > 0;
2803
+ const isWarning = grade === "UNVERIFIED" || grade === "PARTIAL";
2804
+ const statusColor = isError ? theme.error : isWarning ? theme.warning : theme.success;
2805
+ const headerColor = isError ? theme.error : projectColor(item.project);
2806
+ return toolStatusHeader({
2807
+ status: isError ? "error" : isWarning ? "queued" : "done",
2808
+ label: color(headerColor, item.project, true),
2809
+ suffix: `${color(theme.text, ` turn ${item.turnIndex}`)}${grade ? `${dim(context, " \xB7 ")}${color(statusColor, grade, true)}` : ""}`,
2810
+ context,
2811
+ labelAlreadyStyled: true
2459
2812
  });
2460
2813
  }
2461
- var periodicTimer = null;
2462
- function startPeriodicUpdateCheck(currentVersion, onUpdate) {
2463
- if (periodicTimer) return;
2464
- periodicTimer = setInterval(() => {
2465
- fetchLatestVersion().then((latestVersion) => {
2466
- if (!latestVersion) return;
2467
- if (compareVersions(latestVersion, currentVersion) <= 0) return;
2468
- const info = detectInstallInfo();
2469
- if (!info.updateCommand) return;
2470
- writeState({
2471
- lastCheckedAt: Date.now(),
2472
- latestVersion,
2473
- updatePending: true
2474
- });
2475
- onUpdate(
2476
- `Ken just pushed a fresh update \u2014 ${currentVersion} \u2192 ${latestVersion}! Restart ggboss to grab it (or run ${info.updateCommand} if you can't wait).`
2477
- );
2478
- stopPeriodicUpdateCheck();
2479
- }).catch(() => {
2480
- });
2481
- }, CHECK_INTERVAL_MS);
2482
- periodicTimer.unref();
2814
+ function renderWorkerError(item, context) {
2815
+ return [
2816
+ toolStatusHeader({
2817
+ status: "error",
2818
+ label: color(context.theme.error, item.project, true),
2819
+ suffix: dim(context, " worker error"),
2820
+ context,
2821
+ labelAlreadyStyled: true
2822
+ }),
2823
+ messageResponseText(color(context.theme.error, item.message), context)
2824
+ ].join("\n");
2483
2825
  }
2484
- function stopPeriodicUpdateCheck() {
2485
- if (periodicTimer) {
2486
- clearInterval(periodicTimer);
2487
- periodicTimer = null;
2826
+ function renderTaskDispatch(item, context) {
2827
+ const count = item.tasks.length;
2828
+ const lines = [
2829
+ toolStatusHeader({
2830
+ status: "done",
2831
+ label: color(context.theme.text, `Running ${count} task${count === 1 ? "" : "s"}:`, true),
2832
+ context,
2833
+ dotColor: COLORS.primary,
2834
+ labelAlreadyStyled: true
2835
+ })
2836
+ ];
2837
+ for (const task of item.tasks) {
2838
+ lines.push(
2839
+ ` \u2022 ${color(projectColor(task.project), task.project, true)}${dim(context, ": ")}${color(context.theme.text, task.title)}`
2840
+ );
2488
2841
  }
2842
+ return lines.join("\n");
2489
2843
  }
2844
+ function toGGCoderCompletedItem(item) {
2845
+ switch (item.kind) {
2846
+ case "user":
2847
+ return { kind: "user", id: item.id, text: item.text };
2848
+ case "assistant":
2849
+ return {
2850
+ kind: "assistant",
2851
+ id: item.id,
2852
+ text: item.text,
2853
+ thinking: item.thinking,
2854
+ thinkingMs: item.thinkingMs,
2855
+ continuation: item.continuation
2856
+ };
2857
+ case "tool_start":
2858
+ return {
2859
+ kind: "tool_start",
2860
+ id: item.id,
2861
+ toolCallId: item.toolCallId,
2862
+ name: item.name,
2863
+ args: formatBossToolArgsForHistory(item.name, item.args),
2864
+ startedAt: item.startedAt,
2865
+ animateUntil: item.animateUntil,
2866
+ progressOutput: item.progressOutput
2867
+ };
2868
+ case "tool_done":
2869
+ return {
2870
+ kind: "tool_done",
2871
+ id: item.id,
2872
+ name: item.name,
2873
+ args: formatBossToolArgsForHistory(item.name, item.args),
2874
+ result: formatBossToolResultForHistory(item.name, item.result, item.isError),
2875
+ isError: item.isError,
2876
+ durationMs: item.durationMs,
2877
+ details: item.details
2878
+ };
2879
+ case "info":
2880
+ return { kind: "info", id: item.id, text: item.text };
2881
+ case "compacting":
2882
+ return { kind: "compacting", id: item.id };
2883
+ case "compacted":
2884
+ return {
2885
+ kind: "compacted",
2886
+ id: item.id,
2887
+ originalCount: item.originalCount,
2888
+ newCount: item.newCount,
2889
+ tokensBefore: item.tokensBefore,
2890
+ tokensAfter: item.tokensAfter
2891
+ };
2892
+ case "stopped":
2893
+ return { kind: "stopped", id: item.id, text: item.text };
2894
+ }
2895
+ }
2896
+ function formatBossToolArgsForHistory(name, args) {
2897
+ switch (name) {
2898
+ case "list_workers":
2899
+ return { action: "workers" };
2900
+ case "get_worker_status":
2901
+ case "get_worker_summary":
2902
+ return { action: String(args.project ?? "") };
2903
+ case "prompt_worker": {
2904
+ const project = String(args.project ?? "");
2905
+ const message = String(args.message ?? "").replace(/\s+/gu, " ");
2906
+ const fresh = args.fresh === true ? "fresh \xB7 " : "";
2907
+ const detail = project ? `${fresh}${project} \xB7 ${message}` : `${fresh}${message}`;
2908
+ return {
2909
+ action: truncatePlain(detail, Math.max(20, contextlessPromptWorkerDetailLen(project)))
2910
+ };
2911
+ }
2912
+ default:
2913
+ return args;
2914
+ }
2915
+ }
2916
+ function formatBossToolResultForHistory(name, result, isError) {
2917
+ if (isError) return result;
2918
+ const inline = formatBossToolInlineForHistory(name, result);
2919
+ if (!inline) return result;
2920
+ return typeof inline === "string" ? inline : inline.text;
2921
+ }
2922
+ function formatBossToolInlineForHistory(name, result) {
2923
+ switch (name) {
2924
+ case "list_workers": {
2925
+ const lines = result.split("\n").filter((lineText) => lineText.startsWith("-"));
2926
+ return `${lines.length} worker${lines.length === 1 ? "" : "s"}`;
2927
+ }
2928
+ case "prompt_worker": {
2929
+ if (result.includes("currently working")) return "busy \u2014 skipped";
2930
+ if (result.includes("Unknown project")) return "unknown project";
2931
+ return "dispatched";
2932
+ }
2933
+ case "get_worker_status": {
2934
+ const parts = result.split(":");
2935
+ if (parts.length < 2) return void 0;
2936
+ return parts.slice(1).join(":").trim();
2937
+ }
2938
+ case "get_worker_summary": {
2939
+ const turnMatch = result.match(/Turn:\s*(\d+)/u);
2940
+ const toolsMatch = result.match(/Tools used:\s*(.+)/u);
2941
+ const tools = toolsMatch ? toolsMatch[1] : "";
2942
+ const toolCount = tools && tools !== "(no tools used)" ? tools.split(",").length : 0;
2943
+ const turn = turnMatch ? `turn ${turnMatch[1]}` : void 0;
2944
+ const toolSummary = toolCount > 0 ? `${toolCount} tool${toolCount === 1 ? "" : "s"}` : void 0;
2945
+ return [turn, toolSummary].filter(Boolean).join(" \xB7 ") || void 0;
2946
+ }
2947
+ default:
2948
+ return void 0;
2949
+ }
2950
+ }
2951
+ function contextlessPromptWorkerDetailLen(project) {
2952
+ const cols = process.stdout.columns ?? 80;
2953
+ const fixed = 2 + 13 + 1 + project.length + 3 + 1 + 1 + 11 + 6;
2954
+ return Math.max(20, cols - fixed);
2955
+ }
2956
+ function toolStatusHeader({
2957
+ status,
2958
+ label,
2959
+ suffix = "",
2960
+ context,
2961
+ dotColor,
2962
+ labelAlreadyStyled = false
2963
+ }) {
2964
+ const resolvedDotColor = dotColor ?? (status === "error" ? context.theme.error : status === "done" ? context.theme.success : status === "queued" ? context.theme.warning : context.theme.spinnerColor);
2965
+ const indicator = status === "running" ? "\u280B" : "\u23FA";
2966
+ const labelText = labelAlreadyStyled ? label : color(context.theme.toolName, label, true);
2967
+ return `${RESPONSE_LEFT_PADDING}${color(resolvedDotColor, indicator)} ${labelText}${suffix}`;
2968
+ }
2969
+ function messageResponseText(text, context) {
2970
+ const [first, ...rest] = wrapPlain(text, Math.max(10, context.columns - 8)).split("\n");
2971
+ return [
2972
+ `${RESPONSE_LEFT_PADDING}${dim(context, " \u23BF ")}${first ?? ""}`,
2973
+ ...rest.map((lineText) => `${RESPONSE_LEFT_PADDING}${dim(context, " ")}${lineText}`)
2974
+ ].join("\n");
2975
+ }
2976
+ function renderRoundNoticeBox(lines, context, borderColor) {
2977
+ const width = Math.max(
2978
+ 4,
2979
+ Math.min(
2980
+ context.columns - RESPONSE_LEFT_PADDING.length,
2981
+ Math.max(...lines.map((lineText) => stripAnsi(lineText).length)) + 4
2982
+ )
2983
+ );
2984
+ const contentWidth = Math.max(1, width - 4);
2985
+ const top = `${color(borderColor, "\u256D")}${color(borderColor, "\u2500".repeat(width - 2))}${color(
2986
+ borderColor,
2987
+ "\u256E"
2988
+ )}`;
2989
+ const bottom = `${color(borderColor, "\u2570")}${color(borderColor, "\u2500".repeat(width - 2))}${color(
2990
+ borderColor,
2991
+ "\u256F"
2992
+ )}`;
2993
+ const body = lines.flatMap((lineText) => wrapPlain(lineText, contentWidth).split("\n")).map((lineText) => {
2994
+ const plainLength = stripAnsi(lineText).length;
2995
+ return `${color(borderColor, "\u2502")} ${lineText}${" ".repeat(Math.max(0, contentWidth - plainLength))} ${color(borderColor, "\u2502")}`;
2996
+ });
2997
+ return indent([top, ...body, bottom].join("\n"), RESPONSE_LEFT_PADDING);
2998
+ }
2999
+
3000
+ // src/auto-update.ts
3001
+ init_esm_shims();
3002
+ import path4 from "path";
3003
+ import os2 from "os";
3004
+ var updater = createAutoUpdater({
3005
+ packageName: "@kenkaiiii/gg-boss",
3006
+ stateFilePath: () => path4.join(os2.homedir(), ".gg", "boss", "update-state.json"),
3007
+ periodicMessage: ({ currentVersion, latestVersion, updateCommand }) => `Ken just pushed a fresh update \u2014 ${currentVersion} \u2192 ${latestVersion}! Restart ggboss to grab it (or run ${updateCommand} if you can't wait).`
3008
+ });
3009
+ var checkAndAutoUpdate = updater.checkAndAutoUpdate;
3010
+ var getPendingUpdate = updater.getPendingUpdate;
3011
+ var startPeriodicUpdateCheck = updater.startPeriodicUpdateCheck;
3012
+ var stopPeriodicUpdateCheck = updater.stopPeriodicUpdateCheck;
2490
3013
 
2491
3014
  // src/orchestrator-app.tsx
2492
- var import_jsx_runtime10 = __toESM(require_jsx_runtime(), 1);
3015
+ var import_jsx_runtime14 = __toESM(require_jsx_runtime(), 1);
2493
3016
  function BossApp(props) {
2494
3017
  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 }) }) }) });
3018
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(TerminalSizeProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ThemeContext.Provider, { value: theme, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(AnimationProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(BossAppInner, { ...props }) }) }) });
2496
3019
  }
2497
- function BossAppInner({ boss, resetUI }) {
3020
+ function BossAppInner({ boss, resetUI, terminalHistoryPrinter }) {
2498
3021
  const state = useBossState();
3022
+ const theme = useTheme();
2499
3023
  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);
3024
+ const { stdout, write: writeStdout } = use_stdout_default();
3025
+ const { columns, rows } = useTerminalSize();
3026
+ const runStartRef = (0, import_react14.useRef)(null);
2503
3027
  runStartRef.current = state.runStartMs;
2504
- const charCountRef = (0, import_react10.useRef)(0);
3028
+ const charCountRef = (0, import_react14.useRef)(0);
2505
3029
  charCountRef.current = state.streaming?.text.length ?? 0;
2506
- const realTokensAccumRef = (0, import_react10.useRef)(0);
3030
+ const realTokensAccumRef = (0, import_react14.useRef)(0);
2507
3031
  realTokensAccumRef.current = state.bossInputTokens;
2508
- const [lastUserMessage, setLastUserMessage] = (0, import_react10.useState)("");
3032
+ const [lastUserMessage, setLastUserMessage] = (0, import_react14.useState)("");
2509
3033
  const overlay = state.overlay;
2510
- const [currentRadio, setCurrentRadio] = (0, import_react10.useState)(() => getCurrentStation());
2511
- const [updatePending, setUpdatePending] = (0, import_react10.useState)(
3034
+ const [currentRadio, setCurrentRadio] = (0, import_react14.useState)(() => getCurrentStation());
3035
+ const [updatePending, setUpdatePending] = (0, import_react14.useState)(
2512
3036
  () => getPendingUpdate(VERSION) !== null
2513
3037
  );
2514
- (0, import_react10.useEffect)(() => {
3038
+ (0, import_react14.useEffect)(() => {
2515
3039
  startPeriodicUpdateCheck(VERSION, (msg) => {
2516
3040
  bossStore.appendUpdateNotice(msg);
2517
3041
  setUpdatePending(true);
@@ -2519,8 +3043,8 @@ function BossAppInner({ boss, resetUI }) {
2519
3043
  return () => stopPeriodicUpdateCheck();
2520
3044
  }, []);
2521
3045
  const workersRunning = state.workers.filter((w) => w.status === "working").length;
2522
- const titlePrevRef = (0, import_react10.useRef)("");
2523
- (0, import_react10.useEffect)(() => {
3046
+ const titlePrevRef = (0, import_react14.useRef)("");
3047
+ (0, import_react14.useEffect)(() => {
2524
3048
  if (!stdout) return;
2525
3049
  let title;
2526
3050
  if (workersRunning > 0) {
@@ -2536,45 +3060,83 @@ function BossAppInner({ boss, resetUI }) {
2536
3060
  stdout.write(`\x1B]0;${title}\x1B\\`);
2537
3061
  }
2538
3062
  }, [stdout, workersRunning, state.phase]);
2539
- (0, import_react10.useEffect)(() => {
3063
+ (0, import_react14.useEffect)(() => {
2540
3064
  return () => {
2541
3065
  stdout?.write(`\x1B]0;GG Boss\x1B\\`);
2542
3066
  };
2543
3067
  }, [stdout]);
2544
- const staticItems = (0, import_react10.useMemo)(
2545
- () => [{ kind: "banner", id: "banner" }, ...state.history],
2546
- [state.history]
3068
+ const liveItems = state.liveItems;
3069
+ const terminalHistoryPrinterRef = (0, import_react14.useRef)(
3070
+ terminalHistoryPrinter ?? createBossTerminalHistoryPrinter({ stream: stdout })
2547
3071
  );
2548
- const openOverlay = (0, import_react10.useCallback)(
3072
+ const terminalHistoryContext = {
3073
+ theme,
3074
+ columns,
3075
+ version: VERSION,
3076
+ model: state.bossModel,
3077
+ provider: state.bossProvider,
3078
+ cwd: process.cwd()
3079
+ };
3080
+ const printedHistoryIdsRef = (0, import_react14.useRef)(/* @__PURE__ */ new Set());
3081
+ (0, import_react14.useEffect)(() => {
3082
+ const printer = terminalHistoryPrinterRef.current;
3083
+ const pending = state.history.filter((item) => !printedHistoryIdsRef.current.has(item.id));
3084
+ if (pending.length === 0) return;
3085
+ printer.print(pending, terminalHistoryContext, { write: writeStdout });
3086
+ for (const item of pending) printedHistoryIdsRef.current.add(item.id);
3087
+ }, [columns, state.bossModel, state.bossProvider, state.history, theme, writeStdout]);
3088
+ const overlayResetTimerRef = (0, import_react14.useRef)(null);
3089
+ (0, import_react14.useEffect)(() => {
3090
+ return () => {
3091
+ if (overlayResetTimerRef.current) clearTimeout(overlayResetTimerRef.current);
3092
+ };
3093
+ }, []);
3094
+ const scheduleOverlayReset = (0, import_react14.useCallback)(() => {
3095
+ if (!resetUI) return;
3096
+ if (overlayResetTimerRef.current) clearTimeout(overlayResetTimerRef.current);
3097
+ overlayResetTimerRef.current = setTimeout(() => {
3098
+ overlayResetTimerRef.current = null;
3099
+ resetUI();
3100
+ }, 0);
3101
+ }, [resetUI]);
3102
+ const openOverlay = (0, import_react14.useCallback)(
2549
3103
  (next) => {
2550
3104
  bossStore.setOverlay(next);
2551
- if (resetUI) resetUI();
3105
+ scheduleOverlayReset();
2552
3106
  },
2553
- [resetUI]
3107
+ [scheduleOverlayReset]
2554
3108
  );
2555
- const closeOverlay = (0, import_react10.useCallback)(() => {
3109
+ const closeOverlay = (0, import_react14.useCallback)(() => {
2556
3110
  bossStore.setOverlay(null);
2557
- if (resetUI) resetUI();
2558
- }, [resetUI]);
3111
+ scheduleOverlayReset();
3112
+ }, [scheduleOverlayReset]);
2559
3113
  void stdout;
2560
3114
  const handleDoubleExit = useDoublePress(
2561
3115
  (pending) => bossStore.setExitPending(pending),
2562
3116
  () => exit()
2563
3117
  );
2564
- (0, import_react10.useEffect)(() => {
3118
+ (0, import_react14.useEffect)(() => {
2565
3119
  if (state.pendingFlush.length > 0) {
2566
3120
  bossStore.commitPendingFlush();
2567
3121
  }
2568
3122
  }, [state.flushGeneration, state.pendingFlush.length]);
3123
+ const handleAbort = (0, import_react14.useCallback)(() => {
3124
+ if (state.phase === "working") {
3125
+ boss.abort();
3126
+ return;
3127
+ }
3128
+ handleDoubleExit();
3129
+ }, [boss, handleDoubleExit, state.phase]);
2569
3130
  use_input_default((input, key) => {
3131
+ if (key.ctrl && input === "c" && overlay) {
3132
+ handleAbort();
3133
+ return;
3134
+ }
2570
3135
  if (key.ctrl && input === "t") {
2571
3136
  if (overlay === "tasks") closeOverlay();
2572
3137
  else openOverlay("tasks");
2573
3138
  return;
2574
3139
  }
2575
- if (key.escape && state.phase === "working") {
2576
- boss.abort();
2577
- }
2578
3140
  });
2579
3141
  const handleSlashCommand = async (value) => {
2580
3142
  const parsed = parseSlash(value);
@@ -2591,7 +3153,7 @@ function BossAppInner({ boss, resetUI }) {
2591
3153
  return true;
2592
3154
  case "clear":
2593
3155
  bossStore.clearHistory();
2594
- resetUI?.();
3156
+ resetUI?.("session-clear");
2595
3157
  await boss.resetConversation();
2596
3158
  bossStore.appendInfo("Session cleared.", "info");
2597
3159
  return true;
@@ -2622,11 +3184,11 @@ function BossAppInner({ boss, resetUI }) {
2622
3184
  }
2623
3185
  const provider = value.slice(0, colon);
2624
3186
  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
- }
3187
+ const switchPromise = overlay === "model-boss" ? boss.switchBossModel(provider, model) : overlay === "model-workers" ? boss.switchWorkerModel(provider, model) : Promise.resolve();
3188
+ void switchPromise.catch((err) => {
3189
+ const message = err instanceof Error ? err.message : String(err);
3190
+ bossStore.appendInfo(`Model switch failed: ${message}`, "error");
3191
+ });
2630
3192
  closeOverlay();
2631
3193
  };
2632
3194
  const handleSubmit = (value) => {
@@ -2636,141 +3198,117 @@ function BossAppInner({ boss, resetUI }) {
2636
3198
  void handleSlashCommand(trimmed);
2637
3199
  return;
2638
3200
  }
2639
- bossStore.appendUser(trimmed);
3201
+ const userItem = bossStore.createUserItem(trimmed);
3202
+ terminalHistoryPrinterRef.current.print([userItem], terminalHistoryContext, {
3203
+ write: writeStdout
3204
+ });
3205
+ printedHistoryIdsRef.current.add(userItem.id);
3206
+ bossStore.queueSubmittedUserItem(userItem);
2640
3207
  setLastUserMessage(trimmed);
2641
3208
  const scoped = scopePrefix2(state.scope) + trimmed;
2642
3209
  boss.enqueueUserMessage(scoped);
2643
3210
  };
2644
- const handleAbort = () => {
2645
- if (state.phase === "working") {
2646
- boss.abort();
2647
- return;
2648
- }
2649
- handleDoubleExit();
2650
- };
3211
+ const activityVisible = state.phase === "working" && state.activityPhase !== "idle";
3212
+ const stallStatusVisible = false;
3213
+ const doneStatus = null;
3214
+ const statusSlotVisible = activityVisible || stallStatusVisible || !!doneStatus;
3215
+ const controlsRows = 7 + (statusSlotVisible ? 1 : 0) + (state.exitPending ? 0 : 1);
3216
+ const availableLiveRows = Math.max(1, rows - controlsRows);
2651
3217
  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 GG Boss (currently ${rows}).` })
3218
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(Box_default, { flexDirection: "column", width: columns, paddingX: 1, marginTop: 1, children: [
3219
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { bold: true, color: COLORS.accent, children: "Terminal too small" }),
3220
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { color: COLORS.primary, children: `Resize to at least 14 rows to use GG Boss (currently ${rows}).` })
2655
3221
  ] });
2656
3222
  }
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();
2737
- }
2738
- }
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
3223
+ const lastPendingHistoryItem = state.pendingFlush[state.pendingFlush.length - 1];
3224
+ const lastHistoryItem = state.history[state.history.length - 1];
3225
+ const livePane = /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3226
+ BossStreamingTurnView,
3227
+ {
3228
+ turn: state.streaming,
3229
+ isRunning: state.phase === "working",
3230
+ liveItems,
3231
+ lastPendingHistoryItem,
3232
+ lastHistoryItem,
3233
+ availableTerminalHeight: availableLiveRows
3234
+ }
3235
+ );
3236
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3237
+ BossChatScreen,
3238
+ {
3239
+ boss,
3240
+ columns,
3241
+ state,
3242
+ overlay,
3243
+ livePane,
3244
+ theme,
3245
+ statusSlotVisible,
3246
+ activityVisible,
3247
+ stallStatusVisible,
3248
+ doneStatus,
3249
+ elapsedMs: state.runStartMs ? Date.now() - state.runStartMs : 0,
3250
+ runStartRef,
3251
+ charCountRef,
3252
+ realTokensAccumRef,
3253
+ lastUserMessage,
3254
+ activeToolNames: (state.streaming?.tools ?? []).filter((tool) => tool.status === "running").map((tool) => tool.name),
3255
+ inputActive: !overlay,
3256
+ isRunning: state.phase === "working",
3257
+ onSubmit: handleSubmit,
3258
+ onAbort: handleAbort,
3259
+ onTab: () => bossStore.cycleScope(),
3260
+ onShiftTab: () => {
3261
+ const next = getNextThinkingLevel(
3262
+ state.bossProvider,
3263
+ state.bossModel,
3264
+ state.bossThinkingLevel
3265
+ );
3266
+ void boss.setBossThinking(next);
3267
+ },
3268
+ commands: BOSS_SLASH_COMMANDS,
3269
+ scopeBadge: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ScopePill, { scope: state.scope }),
3270
+ onCloseOverlay: closeOverlay,
3271
+ onModelSelect: handleModelSelect,
3272
+ currentRadio,
3273
+ onRadioSelect: (value) => {
3274
+ if (value === "off") {
3275
+ stopRadio();
3276
+ setCurrentRadio(null);
3277
+ bossStore.appendInfo("Radio off.", "info");
3278
+ } else {
3279
+ const result = playRadio(value);
3280
+ if (result.ok) {
3281
+ setCurrentRadio(value);
3282
+ const station = RADIO_STATIONS.find((stationInfo) => stationInfo.id === value);
3283
+ bossStore.appendInfo(`Now playing: ${station?.name ?? value}`, "info");
3284
+ } else {
3285
+ bossStore.appendInfo(result.error ?? "Radio failed to start.", "warning");
2757
3286
  }
2758
- )
2759
- ] })
2760
- ] })
2761
- ] });
3287
+ }
3288
+ closeOverlay();
3289
+ },
3290
+ bossModel: state.bossModel,
3291
+ workerModel: state.workerModel,
3292
+ updatePending,
3293
+ currentRadioStationId: currentRadio,
3294
+ radioStations: RADIO_STATIONS,
3295
+ workers: state.workers,
3296
+ pendingMessages: state.pendingUserMessages,
3297
+ formatDuration: formatBossDuration
3298
+ }
3299
+ );
2762
3300
  }
2763
3301
  function ScopePill({ scope }) {
2764
3302
  const theme = useTheme();
2765
3303
  const isAll = scope === "all";
2766
3304
  const bg = isAll ? COLORS.accent : projectColor(scope);
2767
3305
  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: [
3306
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(Text, { children: [
3307
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { color: theme.textDim, children: "Project " }),
3308
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { color: "black", backgroundColor: bg, bold: true, children: ` ${label} ` }),
3309
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(Text, { color: theme.textDim, children: [
2772
3310
  " ",
2773
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { color: theme.primary, children: "Tab" }),
3311
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Text, { color: theme.primary, children: "Tab" }),
2774
3312
  " to switch"
2775
3313
  ] })
2776
3314
  ] });
@@ -2779,396 +3317,89 @@ function scopePrefix2(scope) {
2779
3317
  if (scope === "all") return "[scope:all] ";
2780
3318
  return `[scope:${scope}] `;
2781
3319
  }
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
- );
2943
- }
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
3320
+ function formatBossDuration(durationMs) {
3321
+ const total = Math.max(0, Math.floor(durationMs / 1e3));
3322
+ const minutes = Math.floor(total / 60);
3323
+ const seconds = total % 60;
3324
+ return minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`;
3325
+ }
3326
+ var INK_OPTIONS = {
3327
+ // Match ggcoder's keyboard setup: enable kitty keyboard so Ink can decode
3328
+ // enhanced key events, but keep exitOnCtrlC false so our handlers receive it.
3329
+ kittyKeyboard: {
3330
+ mode: "enabled",
3331
+ flags: ["disambiguateEscapeCodes"]
3332
+ },
3333
+ exitOnCtrlC: false
3334
+ };
3335
+ var DISABLE_MODIFY_OTHER_KEYS = "\x1B[>4;0m";
3336
+ var DISABLE_FOCUS_REPORTING = "\x1B[?1004l";
3337
+ var SCREEN_CLEAR = DISABLE_MODIFY_OTHER_KEYS + "\x1B[2J\x1B[3J\x1B[H";
3338
+ var VIEWPORT_CLEAR = DISABLE_MODIFY_OTHER_KEYS + "\x1B[2J\x1B[H";
3339
+ function renderBossApp(opts) {
3340
+ const terminalHistoryPrinter = createBossTerminalHistoryPrinter({ stream: process.stdout });
3341
+ process.stdout.write(SCREEN_CLEAR);
3342
+ const onProcessExit = () => {
3343
+ try {
3344
+ process.stdout.write(DISABLE_MODIFY_OTHER_KEYS + DISABLE_FOCUS_REPORTING);
3345
+ } catch {
2955
3346
  }
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
3347
  };
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) {
3348
+ process.on("exit", onProcessExit);
3148
3349
  const ref = { instance: null };
3149
- const resetUI = () => {
3350
+ const resetUI = (reason = "viewport") => {
3150
3351
  const old = ref.instance;
3151
3352
  if (!old) return;
3152
- process.stdout.write("\x1B[2J\x1B[H");
3153
3353
  old.unmount();
3154
- ref.instance = render_default(/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(BossApp, { boss: opts.boss, resetUI }), {
3155
- exitOnCtrlC: false
3156
- });
3354
+ if (reason === "resize-redraw") {
3355
+ terminalHistoryPrinter.resetPrinted();
3356
+ process.stdout.write(SCREEN_CLEAR);
3357
+ const snapshot = getBossState();
3358
+ if (snapshot.history.length > 0) {
3359
+ terminalHistoryPrinter.print(snapshot.history, {
3360
+ theme: loadTheme("dark"),
3361
+ columns: Math.max(40, process.stdout.columns ?? 80),
3362
+ version: VERSION,
3363
+ model: snapshot.bossModel,
3364
+ provider: snapshot.bossProvider,
3365
+ cwd: process.cwd()
3366
+ });
3367
+ }
3368
+ } else if (reason === "session-clear") {
3369
+ terminalHistoryPrinter.clear();
3370
+ process.stdout.write(SCREEN_CLEAR);
3371
+ } else {
3372
+ process.stdout.write(VIEWPORT_CLEAR);
3373
+ }
3374
+ ref.instance = render_default(
3375
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
3376
+ BossApp,
3377
+ {
3378
+ boss: opts.boss,
3379
+ resetUI,
3380
+ terminalHistoryPrinter
3381
+ }
3382
+ ),
3383
+ INK_OPTIONS
3384
+ );
3157
3385
  };
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
- });
3386
+ const instance = render_default(
3387
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(BossApp, { boss: opts.boss, resetUI, terminalHistoryPrinter }),
3388
+ INK_OPTIONS
3389
+ );
3165
3390
  ref.instance = instance;
3166
3391
  let resizeTimer = null;
3392
+ let resizeListenerEnabled = false;
3393
+ const enableResizeListener = setTimeout(() => {
3394
+ resizeListenerEnabled = true;
3395
+ }, 1e3);
3167
3396
  const onTerminalResize = () => {
3397
+ if (!resizeListenerEnabled) return;
3168
3398
  if (resizeTimer) clearTimeout(resizeTimer);
3169
3399
  resizeTimer = setTimeout(() => {
3170
3400
  resizeTimer = null;
3171
- resetUI();
3401
+ if (getBossState().phase === "working") return;
3402
+ resetUI("resize-redraw");
3172
3403
  }, 250);
3173
3404
  };
3174
3405
  process.stdout.on("resize", onTerminalResize);
@@ -3183,21 +3414,30 @@ function renderBossApp(opts) {
3183
3414
  const current = ref.instance;
3184
3415
  if (!current) {
3185
3416
  process.stdout.off("resize", onTerminalResize);
3417
+ process.off("exit", onProcessExit);
3418
+ clearTimeout(enableResizeListener);
3186
3419
  if (resizeTimer) clearTimeout(resizeTimer);
3420
+ onProcessExit();
3187
3421
  return;
3188
3422
  }
3189
3423
  await current.waitUntilExit();
3190
3424
  if (ref.instance === current) {
3191
3425
  ref.instance = null;
3192
3426
  process.stdout.off("resize", onTerminalResize);
3427
+ process.off("exit", onProcessExit);
3428
+ clearTimeout(enableResizeListener);
3193
3429
  if (resizeTimer) clearTimeout(resizeTimer);
3430
+ onProcessExit();
3194
3431
  return;
3195
3432
  }
3196
3433
  }
3197
3434
  },
3198
3435
  unmount: () => {
3199
3436
  process.stdout.off("resize", onTerminalResize);
3437
+ process.off("exit", onProcessExit);
3438
+ clearTimeout(enableResizeListener);
3200
3439
  if (resizeTimer) clearTimeout(resizeTimer);
3440
+ onProcessExit();
3201
3441
  ref.instance?.unmount();
3202
3442
  }
3203
3443
  };
@@ -3205,8 +3445,8 @@ function renderBossApp(opts) {
3205
3445
 
3206
3446
  // src/splash.tsx
3207
3447
  init_esm_shims();
3208
- var import_react11 = __toESM(require_react(), 1);
3209
- var import_jsx_runtime11 = __toESM(require_jsx_runtime(), 1);
3448
+ var import_react15 = __toESM(require_react(), 1);
3449
+ var import_jsx_runtime15 = __toESM(require_jsx_runtime(), 1);
3210
3450
  var SPLASH_LINES = [
3211
3451
  " \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
3452
  " \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 +3464,33 @@ function colorForLine(lineIdx, totalLines, offset) {
3224
3464
  return GRADIENT[idx];
3225
3465
  }
3226
3466
  function SplashLogo({ offset }) {
3227
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Box_default, { flexDirection: "column", children: SPLASH_LINES.map((line, i) => {
3467
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Box_default, { flexDirection: "column", children: SPLASH_LINES.map((line, i) => {
3228
3468
  const hue = colorForLine(i, SPLASH_LINES.length, offset);
3229
3469
  const segments = [];
3230
3470
  let buf = "";
3231
3471
  let bufDim = false;
3232
3472
  for (const ch of line) {
3233
- const dim = ch === "\u2591";
3473
+ const dim2 = ch === "\u2591";
3234
3474
  if (segments.length === 0 && buf.length === 0) {
3235
3475
  buf = ch;
3236
- bufDim = dim;
3476
+ bufDim = dim2;
3237
3477
  continue;
3238
3478
  }
3239
- if (dim === bufDim) {
3479
+ if (dim2 === bufDim) {
3240
3480
  buf += ch;
3241
3481
  } else {
3242
3482
  segments.push({ text: buf, dim: bufDim });
3243
3483
  buf = ch;
3244
- bufDim = dim;
3484
+ bufDim = dim2;
3245
3485
  }
3246
3486
  }
3247
3487
  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);
3488
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { children: segments.map((seg, j) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: hue, dimColor: seg.dim, children: seg.text }, j)) }, i);
3249
3489
  }) });
3250
3490
  }
3251
3491
  function SplashScreen({ caption }) {
3252
- const [offset, setOffset] = (0, import_react11.useState)(0);
3253
- (0, import_react11.useEffect)(() => {
3492
+ const [offset, setOffset] = (0, import_react15.useState)(0);
3493
+ (0, import_react15.useEffect)(() => {
3254
3494
  const timer = setInterval(() => {
3255
3495
  setOffset((o) => o + 1);
3256
3496
  }, 120);
@@ -3258,11 +3498,11 @@ function SplashScreen({ caption }) {
3258
3498
  clearInterval(timer);
3259
3499
  };
3260
3500
  }, []);
3261
- const [size, setSize] = (0, import_react11.useState)(() => ({
3501
+ const [size, setSize] = (0, import_react15.useState)(() => ({
3262
3502
  columns: process.stdout.columns ?? 80,
3263
3503
  rows: process.stdout.rows ?? 24
3264
3504
  }));
3265
- (0, import_react11.useEffect)(() => {
3505
+ (0, import_react15.useEffect)(() => {
3266
3506
  const handler = () => setSize({
3267
3507
  columns: process.stdout.columns ?? 80,
3268
3508
  rows: process.stdout.rows ?? 24
@@ -3274,27 +3514,27 @@ function SplashScreen({ caption }) {
3274
3514
  }, []);
3275
3515
  const SPLASH_BLOCK_HEIGHT = SPLASH_LINES.length + 3;
3276
3516
  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: [
3517
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Box_default, { flexDirection: "column", width: size.columns, height: size.rows, alignItems: "center", children: [
3518
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Box_default, { height: verticalPad, flexShrink: 0 }),
3519
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Box_default, { flexDirection: "column", alignItems: "flex-start", flexShrink: 0, children: [
3520
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(SplashLogo, { offset }),
3521
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Box_default, { width: SPLASH_WIDTH, marginTop: 1, justifyContent: "center", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Text, { children: [
3522
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: COLORS.text, bold: true, children: BRAND }),
3523
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Text, { color: COLORS.textDim, children: [
3284
3524
  " v",
3285
3525
  VERSION
3286
3526
  ] }),
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 })
3527
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: COLORS.textDim, children: " \xB7 By " }),
3528
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: COLORS.text, bold: true, children: AUTHOR })
3289
3529
  ] }) }),
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" }) })
3530
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Box_default, { width: SPLASH_WIDTH, justifyContent: "center", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Text, { color: COLORS.textDim, children: caption ?? "Spinning up the orchestrator\u2026" }) })
3291
3531
  ] })
3292
3532
  ] });
3293
3533
  }
3294
3534
  function showSplash(opts) {
3295
3535
  const start = Date.now();
3296
3536
  void playSplashAudio();
3297
- const instance = render_default(/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(SplashScreen, { caption: opts.caption }));
3537
+ const instance = render_default(/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(SplashScreen, { caption: opts.caption }));
3298
3538
  const audioDurationMs = getSplashAudioDurationMs();
3299
3539
  const defaultMinMs = audioDurationMs + 200;
3300
3540
  return {
@@ -3353,12 +3593,12 @@ function parseArgs(argv) {
3353
3593
  return args;
3354
3594
  }
3355
3595
  function printHelpAndExit() {
3356
- const c = (color, text) => source_default.hex(color)(text);
3596
+ const c = (color2, text) => source_default.hex(color2)(text);
3357
3597
  process.stdout.write(
3358
3598
  "\n" + c(COLORS.primary, "GG Boss") + c(COLORS.textDim, " \u2014 orchestrator that drives multiple ggcoder workers from one chat.\n\n") + c(COLORS.text, "Usage\n") + " " + c(COLORS.accent, "ggboss") + c(
3359
3599
  COLORS.textDim,
3360
3600
  " start orchestrator using linked projects\n"
3361
- ) + " " + c(COLORS.accent, "ggboss link") + c(COLORS.textDim, " pick which projects to link (interactive)\n") + " " + c(COLORS.accent, "ggboss telegram") + c(COLORS.textDim, " configure Telegram bot integration\n") + " " + c(COLORS.accent, "ggboss serve") + c(COLORS.textDim, " run the boss over Telegram (no TUI)\n") + " " + c(COLORS.accent, "ggboss continue") + c(COLORS.textDim, " resume the most recent boss session\n") + " " + c(COLORS.accent, "ggboss --resume <id>") + c(COLORS.textDim, " resume a specific boss session\n") + " " + c(COLORS.accent, "ggboss --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")
3601
+ ) + " " + c(COLORS.accent, "ggboss link") + c(COLORS.textDim, " pick which projects to link (interactive)\n") + " " + c(COLORS.accent, "ggboss telegram") + c(COLORS.textDim, " configure Telegram bot integration\n") + " " + c(COLORS.accent, "ggboss serve") + c(COLORS.textDim, " run the boss over Telegram (no TUI)\n") + " " + c(COLORS.accent, "ggboss continue") + c(COLORS.textDim, " resume the most recent boss session\n") + " " + c(COLORS.accent, "ggboss --resume <id>") + c(COLORS.textDim, " resume a specific boss session\n") + " " + c(COLORS.accent, "ggboss --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
3602
  );
3363
3603
  process.exit(0);
3364
3604
  }
@@ -3393,10 +3633,12 @@ async function runServeSubcommand(argv) {
3393
3633
  process.exit(1);
3394
3634
  }
3395
3635
  const settings = await loadSettings();
3396
- const bossProvider = settings.bossProvider ?? "anthropic";
3397
- const bossModel = cliBossModel ?? settings.bossModel ?? "claude-opus-4-7";
3398
- const workerProvider = settings.workerProvider ?? "anthropic";
3399
- const workerModel = cliWorkerModel ?? settings.workerModel ?? "claude-sonnet-4-6";
3636
+ const { bossProvider, bossModel, workerProvider, workerModel } = await resolveBossAuth({
3637
+ bossProvider: settings.bossProvider ?? "anthropic",
3638
+ bossModel: cliBossModel ?? settings.bossModel ?? "claude-opus-4-8",
3639
+ workerProvider: settings.workerProvider ?? "anthropic",
3640
+ workerModel: cliWorkerModel ?? settings.workerModel ?? "claude-sonnet-4-6"
3641
+ });
3400
3642
  await runBossServeMode({
3401
3643
  bossProvider,
3402
3644
  bossModel,
@@ -3406,6 +3648,45 @@ async function runServeSubcommand(argv) {
3406
3648
  telegram: { botToken, userId }
3407
3649
  });
3408
3650
  }
3651
+ var ALL_PROVIDERS = [
3652
+ "anthropic",
3653
+ "openai",
3654
+ "xiaomi",
3655
+ "gemini",
3656
+ "glm",
3657
+ "moonshot",
3658
+ "minimax",
3659
+ "deepseek",
3660
+ "openrouter"
3661
+ ];
3662
+ function bossDefaultModel(provider) {
3663
+ return provider === "anthropic" ? "claude-opus-4-8" : getDefaultModel(provider).id;
3664
+ }
3665
+ async function resolveBossAuth(input) {
3666
+ const auth = new AuthStorage();
3667
+ await auth.load();
3668
+ const stored = await auth.listProviders();
3669
+ const loggedIn = ALL_PROVIDERS.filter((p) => stored.includes(p));
3670
+ if (loggedIn.length === 0) {
3671
+ throw new Error('Not logged in to any provider. Run "ggcoder login" to authenticate.');
3672
+ }
3673
+ const fallback = loggedIn[0];
3674
+ const resolve = (preferredProvider, preferredModel, defaultFor) => {
3675
+ const provider = loggedIn.includes(preferredProvider) ? preferredProvider : fallback;
3676
+ const modelFits = getModel(preferredModel)?.provider === provider;
3677
+ return { provider, model: modelFits ? preferredModel : defaultFor(provider) };
3678
+ };
3679
+ const boss = resolve(input.bossProvider, input.bossModel, bossDefaultModel);
3680
+ const worker = resolve(input.workerProvider, input.workerModel, (p) => getDefaultModel(p).id);
3681
+ const fellBack = boss.provider !== input.bossProvider || worker.provider !== input.workerProvider;
3682
+ return {
3683
+ bossProvider: boss.provider,
3684
+ bossModel: boss.model,
3685
+ workerProvider: worker.provider,
3686
+ workerModel: worker.model,
3687
+ fellBack
3688
+ };
3689
+ }
3409
3690
  async function runOrchestrator(args) {
3410
3691
  if (args.projects.length === 0) {
3411
3692
  const links = await loadLinks();
@@ -3422,10 +3703,30 @@ async function runOrchestrator(args) {
3422
3703
  caption: `Spinning up ${args.projects.length} worker${args.projects.length === 1 ? "" : "s"}\u2026`
3423
3704
  });
3424
3705
  const settings = await loadSettings();
3425
- const finalBossProvider = args.bossProvider ?? settings.bossProvider ?? "anthropic";
3426
- const finalBossModel = args.bossModel ?? settings.bossModel ?? "claude-opus-4-7";
3427
- const finalWorkerProvider = args.workerProvider ?? settings.workerProvider ?? "anthropic";
3428
- const finalWorkerModel = args.workerModel ?? settings.workerModel ?? "claude-sonnet-4-6";
3706
+ const preferredBossProvider = args.bossProvider ?? settings.bossProvider ?? "anthropic";
3707
+ const preferredBossModel = args.bossModel ?? settings.bossModel ?? "claude-opus-4-8";
3708
+ const preferredWorkerProvider = args.workerProvider ?? settings.workerProvider ?? "anthropic";
3709
+ const preferredWorkerModel = args.workerModel ?? settings.workerModel ?? "claude-sonnet-4-6";
3710
+ const {
3711
+ bossProvider: finalBossProvider,
3712
+ bossModel: finalBossModel,
3713
+ workerProvider: finalWorkerProvider,
3714
+ workerModel: finalWorkerModel,
3715
+ fellBack
3716
+ } = await resolveBossAuth({
3717
+ bossProvider: preferredBossProvider,
3718
+ bossModel: preferredBossModel,
3719
+ workerProvider: preferredWorkerProvider,
3720
+ workerModel: preferredWorkerModel
3721
+ });
3722
+ if (fellBack) {
3723
+ log("INFO", "cli", "provider fallback", {
3724
+ preferredBoss: preferredBossProvider,
3725
+ boss: finalBossProvider,
3726
+ preferredWorker: preferredWorkerProvider,
3727
+ worker: finalWorkerProvider
3728
+ });
3729
+ }
3429
3730
  initLogger({
3430
3731
  version: VERSION,
3431
3732
  bossProvider: finalBossProvider,