@kenkaiiii/gg-boss 4.3.154 → 4.3.156
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
CompactionSpinner,
|
|
10
10
|
GGBoss,
|
|
11
11
|
InputArea,
|
|
12
|
+
MODELS,
|
|
12
13
|
MessageResponse,
|
|
13
14
|
ModelSelector,
|
|
14
15
|
SelectList,
|
|
@@ -21,7 +22,9 @@ import {
|
|
|
21
22
|
ToolUseLoader,
|
|
22
23
|
UserMessage,
|
|
23
24
|
bossStore,
|
|
25
|
+
closeLogger,
|
|
24
26
|
getAppPaths,
|
|
27
|
+
getBossState,
|
|
25
28
|
getContextWindow,
|
|
26
29
|
getSplashAudioDurationMs,
|
|
27
30
|
initLogger,
|
|
@@ -32,6 +35,8 @@ import {
|
|
|
32
35
|
render_default,
|
|
33
36
|
require_jsx_runtime,
|
|
34
37
|
require_react,
|
|
38
|
+
saveSettings,
|
|
39
|
+
subscribeToBossStore,
|
|
35
40
|
tasksStore,
|
|
36
41
|
useAnimationActive,
|
|
37
42
|
useAnimationTick,
|
|
@@ -43,7 +48,7 @@ import {
|
|
|
43
48
|
use_app_default,
|
|
44
49
|
use_input_default,
|
|
45
50
|
use_stdout_default
|
|
46
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-KWWLOD25.js";
|
|
47
52
|
import "./chunk-QT366Y52.js";
|
|
48
53
|
import {
|
|
49
54
|
source_default
|
|
@@ -55,7 +60,7 @@ import {
|
|
|
55
60
|
|
|
56
61
|
// src/cli.ts
|
|
57
62
|
init_esm_shims();
|
|
58
|
-
import
|
|
63
|
+
import path5 from "path";
|
|
59
64
|
|
|
60
65
|
// src/links.ts
|
|
61
66
|
init_esm_shims();
|
|
@@ -336,7 +341,7 @@ init_esm_shims();
|
|
|
336
341
|
// package.json
|
|
337
342
|
var package_default = {
|
|
338
343
|
name: "@kenkaiiii/gg-boss",
|
|
339
|
-
version: "4.3.
|
|
344
|
+
version: "4.3.156",
|
|
340
345
|
type: "module",
|
|
341
346
|
description: "Orchestrator agent that drives multiple ggcoder sessions across projects from a single chat",
|
|
342
347
|
license: "MIT",
|
|
@@ -376,6 +381,10 @@ var package_default = {
|
|
|
376
381
|
vitest: "^4.1.4",
|
|
377
382
|
zod: "^4.4.3"
|
|
378
383
|
},
|
|
384
|
+
optionalDependencies: {
|
|
385
|
+
"@huggingface/transformers": "^3.6.0",
|
|
386
|
+
"ogg-opus-decoder": "^1.6.13"
|
|
387
|
+
},
|
|
379
388
|
publishConfig: {
|
|
380
389
|
access: "public"
|
|
381
390
|
}
|
|
@@ -632,7 +641,7 @@ async function runLinkCommand() {
|
|
|
632
641
|
process.stdout.write(source_default.hex(COLORS.textDim)("\nCancelled. No changes saved.\n"));
|
|
633
642
|
return;
|
|
634
643
|
}
|
|
635
|
-
const linked = result.selected.map((
|
|
644
|
+
const linked = result.selected.map((path6) => projects.find((p) => p.path === path6)).filter((p) => Boolean(p)).map((p) => ({ name: p.name, cwd: p.path }));
|
|
636
645
|
await saveLinks({ projects: linked });
|
|
637
646
|
process.stdout.write("\n");
|
|
638
647
|
if (linked.length === 0) {
|
|
@@ -657,6 +666,872 @@ async function runLinkCommand() {
|
|
|
657
666
|
}
|
|
658
667
|
}
|
|
659
668
|
|
|
669
|
+
// src/serve-mode.ts
|
|
670
|
+
init_esm_shims();
|
|
671
|
+
import path3 from "path";
|
|
672
|
+
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
|
+
function getTelegramConfigPath() {
|
|
985
|
+
return path3.join(getAppPaths().agentDir, "boss", "telegram.json");
|
|
986
|
+
}
|
|
987
|
+
async function loadBossTelegramConfig() {
|
|
988
|
+
try {
|
|
989
|
+
const raw = await fs3.readFile(getTelegramConfigPath(), "utf-8");
|
|
990
|
+
const data = JSON.parse(raw);
|
|
991
|
+
if (data.botToken && data.userId) return data;
|
|
992
|
+
return null;
|
|
993
|
+
} catch {
|
|
994
|
+
return null;
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
async function saveBossTelegramConfig(config) {
|
|
998
|
+
const file = getTelegramConfigPath();
|
|
999
|
+
await fs3.mkdir(path3.dirname(file), { recursive: true });
|
|
1000
|
+
await fs3.writeFile(file, JSON.stringify(config, null, 2), { encoding: "utf-8", mode: 384 });
|
|
1001
|
+
}
|
|
1002
|
+
function formatItemForTelegram(item) {
|
|
1003
|
+
switch (item.kind) {
|
|
1004
|
+
case "user":
|
|
1005
|
+
case "tool":
|
|
1006
|
+
case "worker_event":
|
|
1007
|
+
return null;
|
|
1008
|
+
case "assistant": {
|
|
1009
|
+
const cleaned = stripScopePrefix(item.text).trim();
|
|
1010
|
+
return cleaned ? truncate(cleaned, 1500) : null;
|
|
1011
|
+
}
|
|
1012
|
+
case "worker_error":
|
|
1013
|
+
return `\u2717 *${item.project}* \u2014 ${truncate(item.message, 300)}`;
|
|
1014
|
+
case "info":
|
|
1015
|
+
if (item.level !== "warning" && item.level !== "error") return null;
|
|
1016
|
+
return `${item.level === "error" ? "\u2717 " : "\u26A0 "}_${truncate(item.text, 300)}_`;
|
|
1017
|
+
case "task_dispatch": {
|
|
1018
|
+
if (item.tasks.length === 0) return null;
|
|
1019
|
+
const projects = [...new Set(item.tasks.map((t) => t.project))];
|
|
1020
|
+
if (item.tasks.length === 1) {
|
|
1021
|
+
const t = item.tasks[0];
|
|
1022
|
+
return `\u2192 *${t.project}*: ${truncate(t.title, 140)}`;
|
|
1023
|
+
}
|
|
1024
|
+
return `\u2192 Dispatched ${item.tasks.length} tasks across ${projects.length} project${projects.length === 1 ? "" : "s"}`;
|
|
1025
|
+
}
|
|
1026
|
+
case "update_notice":
|
|
1027
|
+
return `\u2728 ${item.text}`;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
function stripScopePrefix(text) {
|
|
1031
|
+
return text.replace(/^\s*\[scope:[^\]]+\]\s*/, "");
|
|
1032
|
+
}
|
|
1033
|
+
function truncate(text, max) {
|
|
1034
|
+
if (text.length <= max) return text;
|
|
1035
|
+
return text.slice(0, max - 1).trimEnd() + "\u2026";
|
|
1036
|
+
}
|
|
1037
|
+
function scopePrefix(scope) {
|
|
1038
|
+
if (scope === "all") return "[scope:all] ";
|
|
1039
|
+
return `[scope:${scope}] `;
|
|
1040
|
+
}
|
|
1041
|
+
async function runBossServeMode(options) {
|
|
1042
|
+
initLogger({
|
|
1043
|
+
version: VERSION,
|
|
1044
|
+
bossProvider: options.bossProvider,
|
|
1045
|
+
bossModel: options.bossModel,
|
|
1046
|
+
bossThinking: options.bossThinkingLevel,
|
|
1047
|
+
workerProvider: options.workerProvider,
|
|
1048
|
+
workerModel: options.workerModel,
|
|
1049
|
+
projectCount: 0
|
|
1050
|
+
});
|
|
1051
|
+
const links = await loadLinks();
|
|
1052
|
+
if (links.projects.length === 0) {
|
|
1053
|
+
console.error(
|
|
1054
|
+
source_default.hex(COLORS.error)("No linked projects.\n") + source_default.hex(COLORS.textDim)("Run ") + source_default.hex(COLORS.accent)("ggboss link") + source_default.hex(COLORS.textDim)(" first to choose which projects the boss should manage.")
|
|
1055
|
+
);
|
|
1056
|
+
process.exit(1);
|
|
1057
|
+
}
|
|
1058
|
+
const projects = links.projects.map((p) => ({ name: p.name, cwd: p.cwd }));
|
|
1059
|
+
await tasksStore.load();
|
|
1060
|
+
const bot = new TelegramBot({
|
|
1061
|
+
botToken: options.telegram.botToken,
|
|
1062
|
+
allowedUserId: options.telegram.userId
|
|
1063
|
+
});
|
|
1064
|
+
const boss = new GGBoss({
|
|
1065
|
+
bossProvider: options.bossProvider,
|
|
1066
|
+
bossModel: options.bossModel,
|
|
1067
|
+
bossThinkingLevel: options.bossThinkingLevel,
|
|
1068
|
+
workerProvider: options.workerProvider,
|
|
1069
|
+
workerModel: options.workerModel,
|
|
1070
|
+
workerThinkingLevel: options.workerThinkingLevel,
|
|
1071
|
+
projects
|
|
1072
|
+
});
|
|
1073
|
+
await boss.initialize();
|
|
1074
|
+
log("INFO", "serve", "boss initialized", { projects: projects.map((p) => p.name).join(",") });
|
|
1075
|
+
const allowedChatId = options.telegram.userId;
|
|
1076
|
+
const pendingScopeSelections = /* @__PURE__ */ new Map();
|
|
1077
|
+
const pendingModelSelections = /* @__PURE__ */ new Map();
|
|
1078
|
+
let lastHistoryLen = getBossState().history.length;
|
|
1079
|
+
let typingInterval = null;
|
|
1080
|
+
let isStreaming = false;
|
|
1081
|
+
function sendQueued(text) {
|
|
1082
|
+
bot.send(allowedChatId, text).catch((err) => {
|
|
1083
|
+
log("WARN", "telegram", `send failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1084
|
+
bot.sendPlain(allowedChatId, text.replace(/[*_`]/g, "")).catch(() => {
|
|
1085
|
+
});
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
function startTyping() {
|
|
1089
|
+
if (typingInterval) return;
|
|
1090
|
+
bot.sendTyping(allowedChatId).catch(() => {
|
|
1091
|
+
});
|
|
1092
|
+
typingInterval = setInterval(() => {
|
|
1093
|
+
bot.sendTyping(allowedChatId).catch(() => {
|
|
1094
|
+
});
|
|
1095
|
+
}, 4e3);
|
|
1096
|
+
}
|
|
1097
|
+
function stopTyping() {
|
|
1098
|
+
if (typingInterval) {
|
|
1099
|
+
clearInterval(typingInterval);
|
|
1100
|
+
typingInterval = null;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
function flushNewItems(state) {
|
|
1104
|
+
const len = state.history.length;
|
|
1105
|
+
if (len <= lastHistoryLen) return;
|
|
1106
|
+
const fresh = state.history.slice(lastHistoryLen);
|
|
1107
|
+
lastHistoryLen = len;
|
|
1108
|
+
for (const item of fresh) {
|
|
1109
|
+
const formatted = formatItemForTelegram(item);
|
|
1110
|
+
if (formatted) sendQueued(formatted);
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
async function applyModelChoice(target, selected) {
|
|
1114
|
+
try {
|
|
1115
|
+
if (target === "boss") {
|
|
1116
|
+
await boss.switchBossModel(selected.provider, selected.id);
|
|
1117
|
+
await saveSettings({ bossProvider: selected.provider, bossModel: selected.id });
|
|
1118
|
+
await bot.send(allowedChatId, `Boss \u2192 *${selected.name}*`);
|
|
1119
|
+
} else {
|
|
1120
|
+
await boss.switchWorkerModel(selected.provider, selected.id);
|
|
1121
|
+
await saveSettings({ workerProvider: selected.provider, workerModel: selected.id });
|
|
1122
|
+
await bot.send(allowedChatId, `Workers \u2192 *${selected.name}*`);
|
|
1123
|
+
}
|
|
1124
|
+
} catch (err) {
|
|
1125
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1126
|
+
log("WARN", "model_switch", message, { target, model: selected.id });
|
|
1127
|
+
await bot.send(allowedChatId, `Failed to switch ${target}: ${message}`);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
const unsubscribe = subscribeToBossStore(() => {
|
|
1131
|
+
if (getBossState().pendingFlush.length > 0) {
|
|
1132
|
+
bossStore.commitPendingFlush();
|
|
1133
|
+
}
|
|
1134
|
+
const state = getBossState();
|
|
1135
|
+
flushNewItems(state);
|
|
1136
|
+
const streamingNow = state.streaming !== null;
|
|
1137
|
+
if (streamingNow && !isStreaming) {
|
|
1138
|
+
isStreaming = true;
|
|
1139
|
+
startTyping();
|
|
1140
|
+
} else if (!streamingNow && isStreaming) {
|
|
1141
|
+
isStreaming = false;
|
|
1142
|
+
stopTyping();
|
|
1143
|
+
}
|
|
1144
|
+
});
|
|
1145
|
+
bot.onText(async (msg) => {
|
|
1146
|
+
const { text, chatId } = msg;
|
|
1147
|
+
if (chatId !== allowedChatId) return;
|
|
1148
|
+
const pendingScopes = pendingScopeSelections.get(chatId);
|
|
1149
|
+
if (pendingScopes && /^\s*\d+\s*$/.test(text)) {
|
|
1150
|
+
pendingScopeSelections.delete(chatId);
|
|
1151
|
+
const num = parseInt(text.trim(), 10);
|
|
1152
|
+
if (num < 1 || num > pendingScopes.length) {
|
|
1153
|
+
await bot.send(chatId, "Invalid selection. Send /scope to try again.");
|
|
1154
|
+
return;
|
|
1155
|
+
}
|
|
1156
|
+
const chosen = pendingScopes[num - 1];
|
|
1157
|
+
bossStore.setScope(chosen);
|
|
1158
|
+
await bot.send(chatId, `Scope: *${chosen}*`);
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
const pendingModel = pendingModelSelections.get(chatId);
|
|
1162
|
+
if (pendingModel && /^\s*\d+\s*$/.test(text)) {
|
|
1163
|
+
pendingModelSelections.delete(chatId);
|
|
1164
|
+
const num = parseInt(text.trim(), 10);
|
|
1165
|
+
if (num < 1 || num > pendingModel.models.length) {
|
|
1166
|
+
await bot.send(chatId, "Invalid selection. Send /m to try again.");
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
const selected = pendingModel.models[num - 1];
|
|
1170
|
+
await applyModelChoice(pendingModel.target, selected);
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
if (!text.startsWith("/")) {
|
|
1174
|
+
const scoped = scopePrefix(getBossState().scope) + text;
|
|
1175
|
+
boss.enqueueUserMessage(scoped);
|
|
1176
|
+
return;
|
|
1177
|
+
}
|
|
1178
|
+
const parts = text.trim().split(/\s+/);
|
|
1179
|
+
const cmd = parts[0].slice(1).toLowerCase().replace(/@\w+$/, "");
|
|
1180
|
+
if (cmd === "help" || cmd === "start") {
|
|
1181
|
+
await bot.send(chatId, buildTelegramHelpText());
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
if (cmd === "m" || cmd === "model" || cmd === "model-boss" || cmd === "model-workers") {
|
|
1185
|
+
const target = cmd === "model-workers" ? "workers" : "boss";
|
|
1186
|
+
const arg = parts.slice(1).join(" ").trim().toLowerCase();
|
|
1187
|
+
const state = getBossState();
|
|
1188
|
+
const currentId = target === "boss" ? state.bossModel : state.workerModel;
|
|
1189
|
+
if (arg) {
|
|
1190
|
+
const num = parseInt(arg, 10);
|
|
1191
|
+
let match;
|
|
1192
|
+
if (!isNaN(num) && num >= 1 && num <= MODELS.length) {
|
|
1193
|
+
match = MODELS[num - 1];
|
|
1194
|
+
} else {
|
|
1195
|
+
match = MODELS.find(
|
|
1196
|
+
(m) => m.name.toLowerCase().includes(arg) || m.id.toLowerCase().includes(arg)
|
|
1197
|
+
);
|
|
1198
|
+
}
|
|
1199
|
+
if (!match) {
|
|
1200
|
+
await bot.send(chatId, `No model matching "${arg}". Send /${cmd} to see the list.`);
|
|
1201
|
+
return;
|
|
1202
|
+
}
|
|
1203
|
+
await applyModelChoice(target, match);
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
let listText = `*${target === "boss" ? "Boss" : "Worker"} model*
|
|
1207
|
+
`;
|
|
1208
|
+
let lastProvider = "";
|
|
1209
|
+
MODELS.forEach((m, i) => {
|
|
1210
|
+
if (m.provider !== lastProvider) {
|
|
1211
|
+
lastProvider = m.provider;
|
|
1212
|
+
listText += `
|
|
1213
|
+
_${providerLabel(m.provider)}_
|
|
1214
|
+
`;
|
|
1215
|
+
}
|
|
1216
|
+
const active = m.id === currentId ? " \u2190" : "";
|
|
1217
|
+
listText += ` *${i + 1}.* ${m.name}${active}
|
|
1218
|
+
`;
|
|
1219
|
+
});
|
|
1220
|
+
listText += `
|
|
1221
|
+
Send the number, or \`/${cmd} <name>\`.`;
|
|
1222
|
+
pendingModelSelections.set(chatId, { target, models: [...MODELS] });
|
|
1223
|
+
await bot.send(chatId, listText);
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
if (cmd === "scope" || cmd === "s") {
|
|
1227
|
+
const state = getBossState();
|
|
1228
|
+
const arg = parts.slice(1).join(" ").trim().toLowerCase();
|
|
1229
|
+
const names = ["all", ...state.workers.map((w) => w.name)];
|
|
1230
|
+
if (!arg) {
|
|
1231
|
+
const lines = names.map((n, i) => {
|
|
1232
|
+
const active = n === state.scope ? " \u2190" : "";
|
|
1233
|
+
const label = n === "all" ? "*All*" : `*${n}*`;
|
|
1234
|
+
return `*${i + 1}.* ${label}${active}`;
|
|
1235
|
+
});
|
|
1236
|
+
pendingScopeSelections.set(chatId, names);
|
|
1237
|
+
await bot.send(
|
|
1238
|
+
chatId,
|
|
1239
|
+
`*Scope* \u2014 current: *${state.scope}*
|
|
1240
|
+
|
|
1241
|
+
${lines.join("\n")}
|
|
1242
|
+
|
|
1243
|
+
Send the number, or \`/scope <name>\`.`
|
|
1244
|
+
);
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
const num = parseInt(arg, 10);
|
|
1248
|
+
let chosen;
|
|
1249
|
+
if (!isNaN(num) && num >= 1 && num <= names.length) {
|
|
1250
|
+
chosen = names[num - 1];
|
|
1251
|
+
} else {
|
|
1252
|
+
const exact = names.find((n) => n.toLowerCase() === arg);
|
|
1253
|
+
chosen = exact ?? names.find((n) => n.toLowerCase().includes(arg)) ?? null;
|
|
1254
|
+
}
|
|
1255
|
+
if (!chosen) {
|
|
1256
|
+
await bot.send(chatId, `No scope matching "${arg}". Send /scope to see the list.`);
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
1259
|
+
bossStore.setScope(chosen);
|
|
1260
|
+
await bot.send(chatId, `Scope: *${chosen}*`);
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
if (cmd === "status") {
|
|
1264
|
+
const state = getBossState();
|
|
1265
|
+
const lines = [
|
|
1266
|
+
`*${BRAND}* \u2014 ${state.bossModel}`,
|
|
1267
|
+
`Scope *${state.scope}*`,
|
|
1268
|
+
""
|
|
1269
|
+
];
|
|
1270
|
+
lines.push("*Workers*");
|
|
1271
|
+
for (const w of state.workers) {
|
|
1272
|
+
const dot = w.status === "working" ? "\u25CF" : w.status === "error" ? "\u2717" : "\u25CB";
|
|
1273
|
+
lines.push(` ${dot} *${w.name}* \u2014 _${w.status}_`);
|
|
1274
|
+
}
|
|
1275
|
+
const tasks = tasksStore.list();
|
|
1276
|
+
const open = tasks.filter((t) => t.status === "pending" || t.status === "in_progress").length;
|
|
1277
|
+
lines.push("");
|
|
1278
|
+
lines.push(`Tasks ${open} open \xB7 ${tasks.length} total`);
|
|
1279
|
+
await bot.send(chatId, lines.join("\n"));
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1282
|
+
if (cmd === "cancel") {
|
|
1283
|
+
boss.abort();
|
|
1284
|
+
await bot.send(chatId, "_Aborted current boss turn._");
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
if (cmd === "new" || cmd === "n") {
|
|
1288
|
+
await boss.newSession();
|
|
1289
|
+
await bot.send(chatId, "\u2500\u2500 *New session* \u2500\u2500");
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
if (cmd === "tasks") {
|
|
1293
|
+
const tasks = tasksStore.list();
|
|
1294
|
+
if (tasks.length === 0) {
|
|
1295
|
+
await bot.send(chatId, "_No tasks._");
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1298
|
+
const lines = tasks.slice(0, 30).map((t, i) => {
|
|
1299
|
+
const status = t.status.replace("_", " ");
|
|
1300
|
+
return `*${i + 1}.* [${status}] *${t.project}* \u2014 ${t.description.split("\n")[0]}`;
|
|
1301
|
+
});
|
|
1302
|
+
await bot.send(chatId, `*Tasks*
|
|
1303
|
+
|
|
1304
|
+
${lines.join("\n")}`);
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
boss.enqueueUserMessage(text);
|
|
1308
|
+
});
|
|
1309
|
+
bot.onVoice(async (msg) => {
|
|
1310
|
+
const { chatId } = msg;
|
|
1311
|
+
if (chatId !== allowedChatId) return;
|
|
1312
|
+
try {
|
|
1313
|
+
if (!isModelLoaded()) {
|
|
1314
|
+
await bot.send(
|
|
1315
|
+
chatId,
|
|
1316
|
+
"Setting up voice transcription \u2014 downloading Whisper model. This only happens once."
|
|
1317
|
+
);
|
|
1318
|
+
setProgressCallback((info) => {
|
|
1319
|
+
if (info.status === "progress" && info.progress !== void 0) {
|
|
1320
|
+
const pct = Math.round(info.progress);
|
|
1321
|
+
if (pct % 25 === 0 && pct > 0) {
|
|
1322
|
+
bot.sendTyping(chatId).catch(() => {
|
|
1323
|
+
});
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
});
|
|
1327
|
+
}
|
|
1328
|
+
await bot.sendTyping(chatId);
|
|
1329
|
+
const fileUrl = await bot.getFileUrl(msg.fileId);
|
|
1330
|
+
const transcribed = await transcribeVoice(fileUrl);
|
|
1331
|
+
if (!transcribed) {
|
|
1332
|
+
await bot.send(chatId, "_Could not transcribe voice note._");
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
1335
|
+
await bot.send(chatId, `_Voice: "${transcribed}"_`);
|
|
1336
|
+
const scoped = scopePrefix(getBossState().scope) + transcribed;
|
|
1337
|
+
boss.enqueueUserMessage(scoped);
|
|
1338
|
+
} catch (err) {
|
|
1339
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1340
|
+
log("ERROR", "voice", message);
|
|
1341
|
+
const hint = /Cannot find module|Cannot resolve|MODULE_NOT_FOUND/.test(message) ? "\n\nVoice transcription needs the optional `@huggingface/transformers` and `ogg-opus-decoder` packages. Reinstall with `npm i -g @kenkaiiii/gg-boss` and ensure optional deps installed." : "";
|
|
1342
|
+
await bot.send(chatId, `_Voice transcription failed: ${message}_${hint}`);
|
|
1343
|
+
}
|
|
1344
|
+
});
|
|
1345
|
+
process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
|
|
1346
|
+
printBanner({
|
|
1347
|
+
bossModel: options.bossModel,
|
|
1348
|
+
workerModel: options.workerModel,
|
|
1349
|
+
userId: options.telegram.userId,
|
|
1350
|
+
projectCount: projects.length
|
|
1351
|
+
});
|
|
1352
|
+
let shuttingDown = false;
|
|
1353
|
+
const shutdown = async () => {
|
|
1354
|
+
if (shuttingDown) return;
|
|
1355
|
+
shuttingDown = true;
|
|
1356
|
+
console.log(source_default.hex(COLORS.textDim)("\nShutting down..."));
|
|
1357
|
+
bot.stop();
|
|
1358
|
+
stopTyping();
|
|
1359
|
+
unsubscribe();
|
|
1360
|
+
await boss.dispose().catch(() => {
|
|
1361
|
+
});
|
|
1362
|
+
closeLogger();
|
|
1363
|
+
process.exit(0);
|
|
1364
|
+
};
|
|
1365
|
+
process.on("SIGINT", () => void shutdown());
|
|
1366
|
+
process.on("SIGTERM", () => void shutdown());
|
|
1367
|
+
const runPromise = boss.run().catch((err) => {
|
|
1368
|
+
log("ERROR", "boss", err instanceof Error ? err.message : String(err));
|
|
1369
|
+
});
|
|
1370
|
+
await bot.start();
|
|
1371
|
+
await runPromise;
|
|
1372
|
+
}
|
|
1373
|
+
function buildTelegramHelpText() {
|
|
1374
|
+
return [
|
|
1375
|
+
"*GG Boss* \u2014 orchestrator over Telegram",
|
|
1376
|
+
"",
|
|
1377
|
+
"*Commands*",
|
|
1378
|
+
"/scope (/s) \u2014 switch project focus (All / per-worker)",
|
|
1379
|
+
"/m, /model-boss \u2014 switch the orchestrator's model",
|
|
1380
|
+
"/model-workers \u2014 switch every worker's model",
|
|
1381
|
+
"/status \u2014 workers + open tasks",
|
|
1382
|
+
"/tasks \u2014 list tasks",
|
|
1383
|
+
"/new \u2014 fresh boss session",
|
|
1384
|
+
"/cancel \u2014 abort the current boss turn",
|
|
1385
|
+
"/help \u2014 this message",
|
|
1386
|
+
"",
|
|
1387
|
+
"Voice notes are transcribed locally with Whisper and sent as prompts.",
|
|
1388
|
+
"Send any message to talk to the boss."
|
|
1389
|
+
].join("\n");
|
|
1390
|
+
}
|
|
1391
|
+
function providerLabel(provider) {
|
|
1392
|
+
switch (provider) {
|
|
1393
|
+
case "anthropic":
|
|
1394
|
+
return "Anthropic";
|
|
1395
|
+
case "openai":
|
|
1396
|
+
return "OpenAI";
|
|
1397
|
+
case "glm":
|
|
1398
|
+
return "Z.AI";
|
|
1399
|
+
case "moonshot":
|
|
1400
|
+
return "Moonshot";
|
|
1401
|
+
case "minimax":
|
|
1402
|
+
return "MiniMax";
|
|
1403
|
+
case "deepseek":
|
|
1404
|
+
return "DeepSeek";
|
|
1405
|
+
case "openrouter":
|
|
1406
|
+
return "OpenRouter";
|
|
1407
|
+
case "xiaomi":
|
|
1408
|
+
return "Xiaomi";
|
|
1409
|
+
default:
|
|
1410
|
+
return provider;
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
function gradientText(text) {
|
|
1414
|
+
let i = 0;
|
|
1415
|
+
return text.split("").map((ch) => ch === " " ? ch : source_default.hex(GRADIENT[i++ % GRADIENT.length])(ch)).join("");
|
|
1416
|
+
}
|
|
1417
|
+
function printBanner(opts) {
|
|
1418
|
+
console.log();
|
|
1419
|
+
console.log(
|
|
1420
|
+
` ${gradientText(LOGO_LINES[0])}${LOGO_GAP}` + source_default.hex(COLORS.primary).bold(BRAND) + source_default.hex(COLORS.textDim)(` v${VERSION}`) + source_default.hex(COLORS.textDim)(" \xB7 By ") + source_default.white.bold(AUTHOR)
|
|
1421
|
+
);
|
|
1422
|
+
console.log(
|
|
1423
|
+
` ${gradientText(LOGO_LINES[1])}${LOGO_GAP}` + source_default.hex(COLORS.accent)(`Boss: ${opts.bossModel}`)
|
|
1424
|
+
);
|
|
1425
|
+
console.log(
|
|
1426
|
+
` ${gradientText(LOGO_LINES[2])}${LOGO_GAP}` + source_default.hex(COLORS.textDim)(`Workers: ${opts.workerModel}`)
|
|
1427
|
+
);
|
|
1428
|
+
console.log();
|
|
1429
|
+
console.log(
|
|
1430
|
+
source_default.hex(COLORS.textDim)(" Mode ") + source_default.hex(COLORS.accent)("Telegram") + source_default.hex(COLORS.textDim)(" \xB7 User ") + source_default.white(String(opts.userId)) + source_default.hex(COLORS.textDim)(
|
|
1431
|
+
` \xB7 ${opts.projectCount} project${opts.projectCount === 1 ? "" : "s"}`
|
|
1432
|
+
)
|
|
1433
|
+
);
|
|
1434
|
+
console.log();
|
|
1435
|
+
console.log(
|
|
1436
|
+
source_default.hex(COLORS.success)(" Ready. ") + source_default.hex(COLORS.textDim)("Open Telegram and message your bot.")
|
|
1437
|
+
);
|
|
1438
|
+
console.log();
|
|
1439
|
+
console.log(
|
|
1440
|
+
source_default.hex(COLORS.textDim)(" /help ") + source_default.hex(COLORS.textDim)("commands") + source_default.hex(COLORS.textDim)(" /status ") + source_default.hex(COLORS.textDim)("workers + tasks") + source_default.hex(COLORS.textDim)(" /cancel ") + source_default.hex(COLORS.textDim)("abort turn")
|
|
1441
|
+
);
|
|
1442
|
+
console.log();
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
// src/telegram-setup.ts
|
|
1446
|
+
init_esm_shims();
|
|
1447
|
+
import readline2 from "readline/promises";
|
|
1448
|
+
async function runBossTelegramSetup() {
|
|
1449
|
+
process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
|
|
1450
|
+
printSetupBanner();
|
|
1451
|
+
const existing = await loadBossTelegramConfig();
|
|
1452
|
+
if (existing) {
|
|
1453
|
+
console.log(
|
|
1454
|
+
source_default.hex(COLORS.textDim)(" Current config:\n") + source_default.hex(COLORS.textDim)(
|
|
1455
|
+
` Bot token: ${existing.botToken.slice(0, 10)}...${existing.botToken.slice(-4)}
|
|
1456
|
+
`
|
|
1457
|
+
) + source_default.hex(COLORS.textDim)(` User ID: ${existing.userId}
|
|
1458
|
+
`)
|
|
1459
|
+
);
|
|
1460
|
+
}
|
|
1461
|
+
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
1462
|
+
try {
|
|
1463
|
+
console.log(
|
|
1464
|
+
source_default.hex(COLORS.accent)(" Step 1: Bot Token\n") + source_default.hex(COLORS.textDim)(" 1. Open BotFather: ") + source_default.hex(COLORS.primary).underline("https://t.me/BotFather") + "\n" + source_default.hex(COLORS.textDim)(" 2. Send /newbot and follow the prompts\n") + source_default.hex(COLORS.textDim)(" 3. Copy the bot token\n")
|
|
1465
|
+
);
|
|
1466
|
+
const tokenPrompt = existing ? source_default.hex(COLORS.primary)(" Paste bot token (enter to keep current): ") : source_default.hex(COLORS.primary)(" Paste bot token: ");
|
|
1467
|
+
const tokenInput = await rl.question(tokenPrompt);
|
|
1468
|
+
const botToken = tokenInput.trim() || existing?.botToken;
|
|
1469
|
+
if (!botToken) {
|
|
1470
|
+
console.log(source_default.hex(COLORS.error)("\n No bot token provided. Setup cancelled."));
|
|
1471
|
+
return;
|
|
1472
|
+
}
|
|
1473
|
+
if (!/^\d+:[A-Za-z0-9_-]+$/.test(botToken)) {
|
|
1474
|
+
console.log(
|
|
1475
|
+
source_default.hex(COLORS.error)("\n Invalid token format. Expected: 123456789:ABCdef...")
|
|
1476
|
+
);
|
|
1477
|
+
return;
|
|
1478
|
+
}
|
|
1479
|
+
console.log(
|
|
1480
|
+
source_default.hex(COLORS.accent)("\n Step 2: User ID\n") + source_default.hex(COLORS.textDim)(" 1. Open userinfobot: ") + source_default.hex(COLORS.primary).underline("https://t.me/userinfobot") + "\n" + source_default.hex(COLORS.textDim)(" 2. Send any message \u2014 it replies with your numeric ID\n") + source_default.hex(COLORS.textDim)(" Only this user ID can control the boss.\n")
|
|
1481
|
+
);
|
|
1482
|
+
const userPrompt = existing ? source_default.hex(COLORS.primary)(` Your Telegram user ID (enter to keep ${existing.userId}): `) : source_default.hex(COLORS.primary)(" Your Telegram user ID: ");
|
|
1483
|
+
const userInput = await rl.question(userPrompt);
|
|
1484
|
+
const userId = userInput.trim() ? parseInt(userInput.trim(), 10) : existing?.userId;
|
|
1485
|
+
if (!userId || isNaN(userId)) {
|
|
1486
|
+
console.log(source_default.hex(COLORS.error)("\n Invalid user ID. Must be a number."));
|
|
1487
|
+
return;
|
|
1488
|
+
}
|
|
1489
|
+
console.log(source_default.hex(COLORS.textDim)("\n Verifying bot token..."));
|
|
1490
|
+
const verifyRes = await fetch(`https://api.telegram.org/bot${botToken}/getMe`, {
|
|
1491
|
+
method: "POST"
|
|
1492
|
+
});
|
|
1493
|
+
const verifyData = await verifyRes.json();
|
|
1494
|
+
if (!verifyData.ok || !verifyData.result) {
|
|
1495
|
+
console.log(
|
|
1496
|
+
source_default.hex(COLORS.error)(
|
|
1497
|
+
"\n Invalid bot token \u2014 Telegram rejected it. Check and try again."
|
|
1498
|
+
)
|
|
1499
|
+
);
|
|
1500
|
+
return;
|
|
1501
|
+
}
|
|
1502
|
+
const config = { botToken, userId };
|
|
1503
|
+
await saveBossTelegramConfig(config);
|
|
1504
|
+
console.log(
|
|
1505
|
+
source_default.hex(COLORS.success)(
|
|
1506
|
+
`
|
|
1507
|
+
\u2713 Connected to @${verifyData.result.username} (${verifyData.result.first_name})
|
|
1508
|
+
`
|
|
1509
|
+
) + source_default.hex(COLORS.success)(` \u2713 Authorized user ID: ${userId}
|
|
1510
|
+
|
|
1511
|
+
`) + source_default.hex(COLORS.primary)(" To start:\n") + source_default.hex(COLORS.textDim)(" ggboss serve\n")
|
|
1512
|
+
);
|
|
1513
|
+
} finally {
|
|
1514
|
+
rl.close();
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
function gradientText2(text) {
|
|
1518
|
+
let i = 0;
|
|
1519
|
+
return text.split("").map((ch) => ch === " " ? ch : source_default.hex(GRADIENT[i++ % GRADIENT.length])(ch)).join("");
|
|
1520
|
+
}
|
|
1521
|
+
function printSetupBanner() {
|
|
1522
|
+
console.log();
|
|
1523
|
+
console.log(
|
|
1524
|
+
` ${gradientText2(LOGO_LINES[0])}${LOGO_GAP}` + source_default.hex(COLORS.primary).bold(BRAND) + source_default.hex(COLORS.textDim)(` v${VERSION}`) + source_default.hex(COLORS.textDim)(" \xB7 By ") + source_default.white.bold(AUTHOR)
|
|
1525
|
+
);
|
|
1526
|
+
console.log(
|
|
1527
|
+
` ${gradientText2(LOGO_LINES[1])}${LOGO_GAP}` + source_default.hex(COLORS.accent)("Telegram Setup")
|
|
1528
|
+
);
|
|
1529
|
+
console.log(
|
|
1530
|
+
` ${gradientText2(LOGO_LINES[2])}${LOGO_GAP}` + source_default.hex(COLORS.textDim)("Remote Control")
|
|
1531
|
+
);
|
|
1532
|
+
console.log();
|
|
1533
|
+
}
|
|
1534
|
+
|
|
660
1535
|
// src/orchestrator-app.tsx
|
|
661
1536
|
init_esm_shims();
|
|
662
1537
|
var import_react11 = __toESM(require_react(), 1);
|
|
@@ -903,7 +1778,7 @@ function projectColor(name) {
|
|
|
903
1778
|
}
|
|
904
1779
|
|
|
905
1780
|
// src/tool-formatters.ts
|
|
906
|
-
function
|
|
1781
|
+
function truncate2(s, max) {
|
|
907
1782
|
if (max <= 1) return "\u2026";
|
|
908
1783
|
return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
|
|
909
1784
|
}
|
|
@@ -933,13 +1808,13 @@ var bossToolFormatters = {
|
|
|
933
1808
|
return "";
|
|
934
1809
|
case "get_worker_status":
|
|
935
1810
|
case "get_worker_summary":
|
|
936
|
-
return
|
|
1811
|
+
return truncate2(String(args.project ?? ""), 40);
|
|
937
1812
|
case "prompt_worker": {
|
|
938
1813
|
const project = String(args.project ?? "");
|
|
939
1814
|
const message = String(args.message ?? "").replace(/\s+/g, " ");
|
|
940
1815
|
const fresh = args.fresh === true;
|
|
941
1816
|
const maxMsg = promptWorkerDetailLen(project) - (fresh ? 8 : 0);
|
|
942
|
-
const truncMsg =
|
|
1817
|
+
const truncMsg = truncate2(message, Math.max(15, maxMsg));
|
|
943
1818
|
const head = fresh ? "fresh \xB7 " : "";
|
|
944
1819
|
return project ? `${head}${project} \xB7 ${truncMsg}` : `${head}${truncMsg}`;
|
|
945
1820
|
}
|
|
@@ -1456,19 +2331,19 @@ function RadioPicker({
|
|
|
1456
2331
|
// src/auto-update.ts
|
|
1457
2332
|
init_esm_shims();
|
|
1458
2333
|
import { spawn as spawn2 } from "child_process";
|
|
1459
|
-
import
|
|
1460
|
-
import
|
|
2334
|
+
import fs4 from "fs";
|
|
2335
|
+
import path4 from "path";
|
|
1461
2336
|
import os2 from "os";
|
|
1462
2337
|
var PACKAGE_NAME = "@kenkaiiii/gg-boss";
|
|
1463
2338
|
var REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
1464
2339
|
var CHECK_INTERVAL_MS = 60 * 60 * 1e3;
|
|
1465
2340
|
var FETCH_TIMEOUT_MS = 1e4;
|
|
1466
2341
|
function getStateFilePath() {
|
|
1467
|
-
return
|
|
2342
|
+
return path4.join(os2.homedir(), ".gg", "boss", "update-state.json");
|
|
1468
2343
|
}
|
|
1469
2344
|
function readState() {
|
|
1470
2345
|
try {
|
|
1471
|
-
const raw =
|
|
2346
|
+
const raw = fs4.readFileSync(getStateFilePath(), "utf-8");
|
|
1472
2347
|
return JSON.parse(raw);
|
|
1473
2348
|
} catch {
|
|
1474
2349
|
return null;
|
|
@@ -1476,9 +2351,9 @@ function readState() {
|
|
|
1476
2351
|
}
|
|
1477
2352
|
function writeState(state) {
|
|
1478
2353
|
try {
|
|
1479
|
-
const dir =
|
|
1480
|
-
|
|
1481
|
-
|
|
2354
|
+
const dir = path4.dirname(getStateFilePath());
|
|
2355
|
+
fs4.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
2356
|
+
fs4.writeFileSync(getStateFilePath(), JSON.stringify(state));
|
|
1482
2357
|
} catch {
|
|
1483
2358
|
}
|
|
1484
2359
|
}
|
|
@@ -1770,7 +2645,7 @@ function BossAppInner({ boss, resetUI }) {
|
|
|
1770
2645
|
}
|
|
1771
2646
|
bossStore.appendUser(trimmed);
|
|
1772
2647
|
setLastUserMessage(trimmed);
|
|
1773
|
-
const scoped =
|
|
2648
|
+
const scoped = scopePrefix2(state.scope) + trimmed;
|
|
1774
2649
|
boss.enqueueUserMessage(scoped);
|
|
1775
2650
|
};
|
|
1776
2651
|
const handleAbort = () => {
|
|
@@ -1907,7 +2782,7 @@ function ScopePill({ scope }) {
|
|
|
1907
2782
|
] })
|
|
1908
2783
|
] });
|
|
1909
2784
|
}
|
|
1910
|
-
function
|
|
2785
|
+
function scopePrefix2(scope) {
|
|
1911
2786
|
if (scope === "all") return "[scope:all] ";
|
|
1912
2787
|
return `[scope:${scope}] `;
|
|
1913
2788
|
}
|
|
@@ -2448,11 +3323,11 @@ function parseProjectSpec(raw) {
|
|
|
2448
3323
|
const eq = raw.indexOf("=");
|
|
2449
3324
|
if (eq > 0) {
|
|
2450
3325
|
const name = raw.slice(0, eq);
|
|
2451
|
-
const cwd2 =
|
|
3326
|
+
const cwd2 = path5.resolve(raw.slice(eq + 1));
|
|
2452
3327
|
return { name, cwd: cwd2 };
|
|
2453
3328
|
}
|
|
2454
|
-
const cwd =
|
|
2455
|
-
return { name:
|
|
3329
|
+
const cwd = path5.resolve(raw);
|
|
3330
|
+
return { name: path5.basename(cwd), cwd };
|
|
2456
3331
|
}
|
|
2457
3332
|
function parseArgs(argv) {
|
|
2458
3333
|
const args = {
|
|
@@ -2490,10 +3365,54 @@ function printHelpAndExit() {
|
|
|
2490
3365
|
"\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(
|
|
2491
3366
|
COLORS.textDim,
|
|
2492
3367
|
" start orchestrator using linked projects\n"
|
|
2493
|
-
) + " " + c(COLORS.accent, "ggboss link") + c(COLORS.textDim, " pick which projects to link (interactive)\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")
|
|
3368
|
+
) + " " + 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")
|
|
2494
3369
|
);
|
|
2495
3370
|
process.exit(0);
|
|
2496
3371
|
}
|
|
3372
|
+
async function runServeSubcommand(argv) {
|
|
3373
|
+
let cliBotToken;
|
|
3374
|
+
let cliUserId;
|
|
3375
|
+
let cliBossModel;
|
|
3376
|
+
let cliWorkerModel;
|
|
3377
|
+
for (let i = 0; i < argv.length; i++) {
|
|
3378
|
+
const a = argv[i];
|
|
3379
|
+
if (a === "--bot-token") cliBotToken = argv[++i];
|
|
3380
|
+
else if (a === "--user-id") cliUserId = argv[++i];
|
|
3381
|
+
else if (a === "--boss-model") cliBossModel = argv[++i];
|
|
3382
|
+
else if (a === "--worker-model") cliWorkerModel = argv[++i];
|
|
3383
|
+
else if (a === "--help" || a === "-h") {
|
|
3384
|
+
process.stdout.write(
|
|
3385
|
+
"\nggboss serve \u2014 drive the boss from Telegram\n\nOptions\n --bot-token <token> Telegram bot token (or env GG_BOSS_TELEGRAM_BOT_TOKEN)\n --user-id <id> Allowed Telegram user ID (or env GG_BOSS_TELEGRAM_USER_ID)\n --boss-model <id> Override boss model\n --worker-model <id> Override worker model\n\nRun `ggboss telegram` first to save credentials interactively.\n\n"
|
|
3386
|
+
);
|
|
3387
|
+
process.exit(0);
|
|
3388
|
+
} else {
|
|
3389
|
+
throw new Error(`Unknown argument: ${a}`);
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3392
|
+
const saved = await loadBossTelegramConfig();
|
|
3393
|
+
const botToken = cliBotToken ?? process.env.GG_BOSS_TELEGRAM_BOT_TOKEN ?? saved?.botToken;
|
|
3394
|
+
const userIdStr = cliUserId ?? process.env.GG_BOSS_TELEGRAM_USER_ID;
|
|
3395
|
+
const userId = userIdStr ? parseInt(userIdStr, 10) : saved?.userId;
|
|
3396
|
+
if (!botToken || !userId || isNaN(userId)) {
|
|
3397
|
+
process.stderr.write(
|
|
3398
|
+
source_default.hex(COLORS.error)("Telegram not configured.\n\n") + "Run " + source_default.hex(COLORS.primary).bold("ggboss telegram") + " to set up your bot token and user ID.\n\n" + source_default.hex(COLORS.textDim)("Or provide manually:\n") + source_default.hex(COLORS.textDim)(" ggboss serve --bot-token TOKEN --user-id ID\n")
|
|
3399
|
+
);
|
|
3400
|
+
process.exit(1);
|
|
3401
|
+
}
|
|
3402
|
+
const settings = await loadSettings();
|
|
3403
|
+
const bossProvider = settings.bossProvider ?? "anthropic";
|
|
3404
|
+
const bossModel = cliBossModel ?? settings.bossModel ?? "claude-opus-4-7";
|
|
3405
|
+
const workerProvider = settings.workerProvider ?? "anthropic";
|
|
3406
|
+
const workerModel = cliWorkerModel ?? settings.workerModel ?? "claude-sonnet-4-6";
|
|
3407
|
+
await runBossServeMode({
|
|
3408
|
+
bossProvider,
|
|
3409
|
+
bossModel,
|
|
3410
|
+
bossThinkingLevel: settings.bossThinkingLevel,
|
|
3411
|
+
workerProvider,
|
|
3412
|
+
workerModel,
|
|
3413
|
+
telegram: { botToken, userId }
|
|
3414
|
+
});
|
|
3415
|
+
}
|
|
2497
3416
|
async function runOrchestrator(args) {
|
|
2498
3417
|
if (args.projects.length === 0) {
|
|
2499
3418
|
const links = await loadLinks();
|
|
@@ -2556,6 +3475,14 @@ async function main() {
|
|
|
2556
3475
|
await runLinkCommand();
|
|
2557
3476
|
process.exit(0);
|
|
2558
3477
|
}
|
|
3478
|
+
if (argv[0] === "telegram") {
|
|
3479
|
+
await runBossTelegramSetup();
|
|
3480
|
+
process.exit(0);
|
|
3481
|
+
}
|
|
3482
|
+
if (argv[0] === "serve") {
|
|
3483
|
+
await runServeSubcommand(argv.slice(1));
|
|
3484
|
+
return;
|
|
3485
|
+
}
|
|
2559
3486
|
const isContinue = argv[0] === "continue";
|
|
2560
3487
|
const args = parseArgs(isContinue ? argv.slice(1) : argv);
|
|
2561
3488
|
if (isContinue) args.continueRecent = true;
|