@prestyj/boss 4.5.0 → 4.10.1

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
@@ -3,6 +3,7 @@ import { createRequire as __createRequire } from "node:module"; const require =
3
3
  import {
4
4
  AnimationProvider,
5
5
  AssistantMessage,
6
+ AuthStorage,
6
7
  Box_default,
7
8
  ChatControls,
8
9
  ChatInputStack,
@@ -15,6 +16,7 @@ import {
15
16
  MODELS,
16
17
  MessageResponse,
17
18
  RESPONSE_LEFT_PADDING,
19
+ TelegramBot,
18
20
  TerminalSizeProvider,
19
21
  Text,
20
22
  ThemeContext,
@@ -28,17 +30,21 @@ import {
28
30
  buildToolGroupSummary,
29
31
  closeLogger,
30
32
  color,
33
+ createAutoUpdater,
31
34
  dim,
32
35
  formatHistoryWrite,
33
36
  getAppPaths,
34
37
  getBossState,
35
38
  getContextWindow,
39
+ getDefaultModel,
40
+ getModel,
36
41
  getNextThinkingLevel,
37
42
  getSplashAudioDurationMs,
38
43
  getTranscriptItemMarginTop,
39
44
  gradientLine,
40
45
  indent,
41
46
  initLogger,
47
+ isModelLoaded,
42
48
  loadSettings,
43
49
  loadTheme,
44
50
  log,
@@ -48,6 +54,7 @@ import {
48
54
  require_react,
49
55
  saveSettings,
50
56
  serializeCompletedItemToTerminalHistory,
57
+ setProgressCallback,
51
58
  setStreamDiagnostic,
52
59
  shouldSeparateTranscriptItemKinds,
53
60
  shouldTopSpaceStreamingAssistant,
@@ -56,6 +63,7 @@ import {
56
63
  subscribeToBossStore,
57
64
  tasksStore,
58
65
  toolTonePalette,
66
+ transcribeVoice,
59
67
  truncatePlain,
60
68
  useAnimationActive,
61
69
  useAnimationTick,
@@ -68,7 +76,7 @@ import {
68
76
  use_input_default,
69
77
  use_stdout_default,
70
78
  wrapPlain
71
- } from "./chunk-S4K5TGVV.js";
79
+ } from "./chunk-72J6ZGVA.js";
72
80
  import "./chunk-7QDS2KEP.js";
73
81
  import {
74
82
  source_default
@@ -361,7 +369,7 @@ init_esm_shims();
361
369
  // package.json
362
370
  var package_default = {
363
371
  name: "@prestyj/boss",
364
- version: "4.5.0",
372
+ version: "4.10.1",
365
373
  type: "module",
366
374
  description: "Orchestrator agent that drives multiple ezcoder sessions across projects from a single chat",
367
375
  license: "MIT",
@@ -387,6 +395,7 @@ var package_default = {
387
395
  devDependencies: {
388
396
  "@prestyj/agent": "workspace:*",
389
397
  "@prestyj/ai": "workspace:*",
398
+ "@prestyj/core": "workspace:*",
390
399
  "@prestyj/cli": "workspace:*",
391
400
  "@types/node": "^25.6.0",
392
401
  "@types/react": "^19.2.14",
@@ -669,317 +678,6 @@ async function runLinkCommand() {
669
678
  init_esm_shims();
670
679
  import path3 from "path";
671
680
  import fs3 from "fs/promises";
672
-
673
- // src/voice-transcriber.ts
674
- init_esm_shims();
675
- var TARGET_SAMPLE_RATE = 16e3;
676
- var MODEL_ID = "Xenova/whisper-tiny.en";
677
- var transcriber = null;
678
- var loadPromise = null;
679
- var onProgress = null;
680
- function setProgressCallback(cb) {
681
- onProgress = cb;
682
- }
683
- function resample(audio, fromRate, toRate) {
684
- if (fromRate === toRate) return audio;
685
- const ratio = fromRate / toRate;
686
- const newLength = Math.round(audio.length / ratio);
687
- const result = new Float32Array(newLength);
688
- for (let i = 0; i < newLength; i++) {
689
- const srcIndex = i * ratio;
690
- const low = Math.floor(srcIndex);
691
- const high = Math.min(low + 1, audio.length - 1);
692
- const frac = srcIndex - low;
693
- result[i] = audio[low] * (1 - frac) + audio[high] * frac;
694
- }
695
- return result;
696
- }
697
- function downmixToMono(channelData) {
698
- if (channelData.length === 0) return new Float32Array();
699
- if (channelData.length === 1) return channelData[0];
700
- const samples = channelData[0].length;
701
- const out = new Float32Array(samples);
702
- const scale = 1 / channelData.length;
703
- for (let i = 0; i < samples; i++) {
704
- let mixed = 0;
705
- for (const channel of channelData) mixed += channel[i] ?? 0;
706
- out[i] = mixed * scale;
707
- }
708
- return out;
709
- }
710
- async function decodeOggOpus(buffer) {
711
- const { OggOpusDecoder } = await import("ogg-opus-decoder");
712
- const decoder = new OggOpusDecoder();
713
- await decoder.ready;
714
- try {
715
- const decoded = await decoder.decodeFile(buffer);
716
- if (!decoded.channelData?.length || !decoded.channelData[0]?.length) {
717
- throw new Error("Decoded audio is empty");
718
- }
719
- const mono = downmixToMono(decoded.channelData);
720
- return resample(mono, decoded.sampleRate, TARGET_SAMPLE_RATE);
721
- } finally {
722
- decoder.free();
723
- }
724
- }
725
- async function getTranscriber() {
726
- if (transcriber) return transcriber;
727
- if (!loadPromise) {
728
- loadPromise = (async () => {
729
- const { pipeline } = await import("@huggingface/transformers");
730
- const instance = await pipeline("automatic-speech-recognition", MODEL_ID, {
731
- dtype: "fp32",
732
- progress_callback: onProgress ?? void 0
733
- });
734
- transcriber = instance;
735
- return instance;
736
- })();
737
- }
738
- return loadPromise;
739
- }
740
- function isModelLoaded() {
741
- return transcriber !== null;
742
- }
743
- async function transcribeVoice(fileUrl) {
744
- const response = await fetch(fileUrl);
745
- if (!response.ok) throw new Error(`Failed to download voice file: ${response.status}`);
746
- const buffer = new Uint8Array(await response.arrayBuffer());
747
- const pcm = await decodeOggOpus(buffer);
748
- const asr = await getTranscriber();
749
- const result = await asr(pcm);
750
- const text = Array.isArray(result) ? result[0]?.text : result.text;
751
- return (text ?? "").trim();
752
- }
753
-
754
- // src/telegram.ts
755
- init_esm_shims();
756
- var TELEGRAM_API = "https://api.telegram.org";
757
- var MAX_MESSAGE_LENGTH = 4096;
758
- var TelegramBot = class {
759
- token;
760
- allowedUserId;
761
- offset = 0;
762
- running = false;
763
- onMessage = null;
764
- onVoiceMessage = null;
765
- onCallback = null;
766
- onBotAdded = null;
767
- onBotRemoved = null;
768
- constructor(config) {
769
- this.token = config.botToken;
770
- this.allowedUserId = config.allowedUserId;
771
- }
772
- /** Register handler for incoming text messages. */
773
- onText(handler) {
774
- this.onMessage = handler;
775
- }
776
- /** Register handler for incoming voice notes. */
777
- onVoice(handler) {
778
- this.onVoiceMessage = handler;
779
- }
780
- /** Register handler for inline keyboard button presses. */
781
- onCallbackQuery(handler) {
782
- this.onCallback = handler;
783
- }
784
- /** Register handler for when the bot is added to a group. */
785
- onAddedToGroup(handler) {
786
- this.onBotAdded = handler;
787
- }
788
- /** Register handler for when the bot is removed from a group. */
789
- onRemovedFromGroup(handler) {
790
- this.onBotRemoved = handler;
791
- }
792
- /** Start long polling. Blocks until stop() is called. */
793
- async start() {
794
- this.running = true;
795
- const me = await this.apiCall("getMe");
796
- if (!me.ok) {
797
- throw new Error(`Invalid bot token: ${JSON.stringify(me)}`);
798
- }
799
- while (this.running) {
800
- try {
801
- const updates = await this.getUpdates();
802
- for (const update of updates) {
803
- await this.handleUpdate(update);
804
- }
805
- } catch (err) {
806
- if (!this.running) break;
807
- console.error(`[telegram] Poll error: ${err instanceof Error ? err.message : err}`);
808
- await sleep(3e3);
809
- }
810
- }
811
- }
812
- /** Stop long polling. */
813
- stop() {
814
- this.running = false;
815
- }
816
- /** Send a text message to a specific chat. Converts markdown and splits long messages. */
817
- async send(chatId, text, buttons) {
818
- const converted = toTelegramMarkdown(text);
819
- const chunks = splitMessage(converted);
820
- for (let i = 0; i < chunks.length; i++) {
821
- const isLast = i === chunks.length - 1;
822
- const replyMarkup = isLast && buttons ? {
823
- inline_keyboard: buttons.map(
824
- (row) => row.map((b) => ({ text: b.text, callback_data: b.callback_data }))
825
- )
826
- } : void 0;
827
- await this.apiCall("sendMessage", {
828
- chat_id: chatId,
829
- text: chunks[i],
830
- parse_mode: "Markdown",
831
- ...replyMarkup ? { reply_markup: replyMarkup } : {}
832
- });
833
- }
834
- }
835
- /** Send a plain text message (no markdown parsing) to a specific chat. */
836
- async sendPlain(chatId, text) {
837
- const chunks = splitMessage(text);
838
- for (const chunk of chunks) {
839
- await this.apiCall("sendMessage", {
840
- chat_id: chatId,
841
- text: chunk
842
- });
843
- }
844
- }
845
- /** Send a typing indicator to a specific chat. */
846
- async sendTyping(chatId) {
847
- await this.apiCall("sendChatAction", {
848
- chat_id: chatId,
849
- action: "typing"
850
- });
851
- }
852
- /** Get a direct download URL for a Telegram file. */
853
- async getFileUrl(fileId) {
854
- const result = await this.apiCall("getFile", { file_id: fileId });
855
- if (!result.ok) throw new Error(`Failed to get file: ${JSON.stringify(result)}`);
856
- const filePath = result.result.file_path;
857
- return `${TELEGRAM_API}/file/bot${this.token}/${filePath}`;
858
- }
859
- // ── Private ───────────────────────────────────────────
860
- async getUpdates() {
861
- const result = await this.apiCall("getUpdates", {
862
- offset: this.offset,
863
- timeout: 30,
864
- allowed_updates: ["message", "callback_query", "my_chat_member"]
865
- });
866
- if (!result.ok || !Array.isArray(result.result)) return [];
867
- const updates = result.result;
868
- if (updates.length > 0) {
869
- this.offset = updates[updates.length - 1].update_id + 1;
870
- }
871
- return updates;
872
- }
873
- async handleUpdate(update) {
874
- if (update.message) {
875
- const msg = update.message;
876
- if (msg.from.id !== this.allowedUserId) {
877
- return;
878
- }
879
- if (msg.text && this.onMessage) {
880
- this.onMessage({
881
- text: msg.text,
882
- chatId: msg.chat.id,
883
- chatType: msg.chat.type,
884
- chatTitle: msg.chat.title
885
- });
886
- } else if (msg.voice && this.onVoiceMessage) {
887
- this.onVoiceMessage({
888
- fileId: msg.voice.file_id,
889
- duration: msg.voice.duration,
890
- chatId: msg.chat.id,
891
- chatType: msg.chat.type,
892
- chatTitle: msg.chat.title
893
- });
894
- }
895
- }
896
- if (update.my_chat_member) {
897
- const member = update.my_chat_member;
898
- const status = member.new_chat_member.status;
899
- if ((status === "member" || status === "administrator") && this.onBotAdded) {
900
- this.onBotAdded(member.chat.id, member.chat.title);
901
- } else if ((status === "left" || status === "kicked") && this.onBotRemoved) {
902
- this.onBotRemoved(member.chat.id);
903
- }
904
- }
905
- if (update.callback_query) {
906
- const cb = update.callback_query;
907
- if (cb.from.id !== this.allowedUserId) return;
908
- await this.apiCall("answerCallbackQuery", { callback_query_id: cb.id });
909
- if (cb.data && this.onCallback) {
910
- this.onCallback(cb.data, cb.message.chat.id);
911
- }
912
- }
913
- }
914
- async apiCall(method, body) {
915
- const url = `${TELEGRAM_API}/bot${this.token}/${method}`;
916
- const response = await fetch(url, {
917
- method: "POST",
918
- headers: { "Content-Type": "application/json" },
919
- body: body ? JSON.stringify(body) : void 0
920
- });
921
- if (!response.ok) {
922
- return { ok: false };
923
- }
924
- return response.json();
925
- }
926
- };
927
- function toTelegramMarkdown(text) {
928
- const lines = text.split("\n");
929
- const result = [];
930
- let inCodeBlock = false;
931
- for (const line of lines) {
932
- if (line.trimStart().startsWith("```")) {
933
- inCodeBlock = !inCodeBlock;
934
- result.push(line);
935
- continue;
936
- }
937
- if (inCodeBlock) {
938
- result.push(line);
939
- continue;
940
- }
941
- let transformed = line;
942
- const headingMatch = transformed.match(/^(#{1,6})\s+(.+)$/);
943
- if (headingMatch) {
944
- transformed = `*${headingMatch[2]}*`;
945
- result.push(transformed);
946
- continue;
947
- }
948
- if (/^(-{3,}|_{3,}|\*{3,})$/.test(transformed.trim())) {
949
- result.push("");
950
- continue;
951
- }
952
- transformed = transformed.replace(/\*\*(.+?)\*\*/g, "*$1*");
953
- result.push(transformed);
954
- }
955
- return result.join("\n");
956
- }
957
- function splitMessage(text) {
958
- if (text.length <= MAX_MESSAGE_LENGTH) return [text];
959
- const chunks = [];
960
- let remaining = text;
961
- while (remaining.length > 0) {
962
- if (remaining.length <= MAX_MESSAGE_LENGTH) {
963
- chunks.push(remaining);
964
- break;
965
- }
966
- let splitAt = remaining.lastIndexOf("\n", MAX_MESSAGE_LENGTH);
967
- if (splitAt === -1 || splitAt < MAX_MESSAGE_LENGTH * 0.5) {
968
- splitAt = remaining.lastIndexOf(" ", MAX_MESSAGE_LENGTH);
969
- }
970
- if (splitAt === -1 || splitAt < MAX_MESSAGE_LENGTH * 0.5) {
971
- splitAt = MAX_MESSAGE_LENGTH;
972
- }
973
- chunks.push(remaining.slice(0, splitAt));
974
- remaining = remaining.slice(splitAt).trimStart();
975
- }
976
- return chunks;
977
- }
978
- function sleep(ms) {
979
- return new Promise((r) => setTimeout(r, ms));
980
- }
981
-
982
- // src/serve-mode.ts
983
681
  function getTelegramConfigPath() {
984
682
  return path3.join(getAppPaths().agentDir, "boss", "telegram.json");
985
683
  }
@@ -1641,6 +1339,8 @@ var PARTIAL_BLOCKS = [" ", "\u258F", "\u258E", "\u258D", "\u258C", "\u258B", "\u
1641
1339
  var LIGHT_SHADE = "\u2591";
1642
1340
  var BAR_WIDTH = 8;
1643
1341
  var SHORT_MODELS = {
1342
+ "claude-fable-5": "Fable",
1343
+ "claude-mythos-5": "Mythos",
1644
1344
  "claude-opus-4-8": "Opus",
1645
1345
  "claude-sonnet-4-6": "Sonnet",
1646
1346
  "claude-haiku-4-5": "Haiku",
@@ -3311,170 +3011,17 @@ function renderRoundNoticeBox(lines, context, borderColor) {
3311
3011
 
3312
3012
  // src/auto-update.ts
3313
3013
  init_esm_shims();
3314
- import { spawn as spawn2 } from "child_process";
3315
- import fs4 from "fs";
3316
3014
  import path4 from "path";
3317
3015
  import os2 from "os";
3318
- var PACKAGE_NAME = "@prestyj/boss";
3319
- var REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
3320
- var CHECK_INTERVAL_MS = 60 * 60 * 1e3;
3321
- var FETCH_TIMEOUT_MS = 1e4;
3322
- function getStateFilePath() {
3323
- return path4.join(os2.homedir(), ".ezcoder", "boss", "update-state.json");
3324
- }
3325
- function readState() {
3326
- try {
3327
- const raw = fs4.readFileSync(getStateFilePath(), "utf-8");
3328
- return JSON.parse(raw);
3329
- } catch {
3330
- return null;
3331
- }
3332
- }
3333
- function writeState(state) {
3334
- try {
3335
- const dir = path4.dirname(getStateFilePath());
3336
- fs4.mkdirSync(dir, { recursive: true, mode: 448 });
3337
- fs4.writeFileSync(getStateFilePath(), JSON.stringify(state));
3338
- } catch {
3339
- }
3340
- }
3341
- function compareVersions(a, b) {
3342
- const pa = a.split(".").map(Number);
3343
- const pb = b.split(".").map(Number);
3344
- for (let i = 0; i < 3; i++) {
3345
- const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
3346
- if (diff !== 0) return diff;
3347
- }
3348
- return 0;
3349
- }
3350
- function detectInstallInfo() {
3351
- const scriptPath = (process.argv[1] ?? "").replace(/\\/g, "/");
3352
- if (scriptPath.includes("/_npx/")) {
3353
- return { packageManager: "unknown" /* UNKNOWN */, updateCommand: null };
3354
- }
3355
- if (scriptPath.includes("/.pnpm") || scriptPath.includes("/pnpm/global")) {
3356
- return {
3357
- packageManager: "pnpm" /* PNPM */,
3358
- updateCommand: `pnpm add -g ${PACKAGE_NAME}@latest`
3359
- };
3360
- }
3361
- if (scriptPath.includes("/.yarn/") || scriptPath.includes("/yarn/global")) {
3362
- return {
3363
- packageManager: "yarn" /* YARN */,
3364
- updateCommand: `yarn global add ${PACKAGE_NAME}@latest`
3365
- };
3366
- }
3367
- return {
3368
- packageManager: "npm" /* NPM */,
3369
- updateCommand: `npm install -g ${PACKAGE_NAME}@latest`
3370
- };
3371
- }
3372
- async function fetchLatestVersion() {
3373
- try {
3374
- const controller = new AbortController();
3375
- const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
3376
- const response = await fetch(REGISTRY_URL, { signal: controller.signal });
3377
- clearTimeout(timeout);
3378
- const data = await response.json();
3379
- const version = data.version?.trim();
3380
- return version && /^\d+\.\d+\.\d+/.test(version) ? version : null;
3381
- } catch {
3382
- return null;
3383
- }
3384
- }
3385
- function performUpdateInBackground(command) {
3386
- try {
3387
- const parts = command.split(" ");
3388
- const child = spawn2(parts[0], parts.slice(1), {
3389
- detached: true,
3390
- stdio: "ignore",
3391
- env: { ...process.env, npm_config_loglevel: "silent" }
3392
- });
3393
- child.unref();
3394
- } catch {
3395
- }
3396
- }
3397
- function checkAndAutoUpdate(currentVersion) {
3398
- try {
3399
- const state = readState();
3400
- let message = null;
3401
- if (state?.updatePending && state.latestVersion) {
3402
- if (compareVersions(state.latestVersion, currentVersion) > 0) {
3403
- const info = detectInstallInfo();
3404
- if (info.updateCommand) {
3405
- performUpdateInBackground(info.updateCommand);
3406
- message = `Nolan just shipped ${state.latestVersion}! Installing in the background \u2014 takes effect next launch.`;
3407
- writeState({
3408
- ...state,
3409
- lastCheckedAt: Date.now(),
3410
- updatePending: false,
3411
- lastUpdateAttempt: Date.now()
3412
- });
3413
- }
3414
- } else {
3415
- writeState({ ...state, updatePending: false });
3416
- }
3417
- }
3418
- const shouldCheck = !state || Date.now() - state.lastCheckedAt > CHECK_INTERVAL_MS;
3419
- if (shouldCheck) scheduleBackgroundCheck(currentVersion);
3420
- return message;
3421
- } catch {
3422
- return null;
3423
- }
3424
- }
3425
- function getPendingUpdate(currentVersion) {
3426
- try {
3427
- const state = readState();
3428
- if (!state?.latestVersion) return null;
3429
- if (compareVersions(state.latestVersion, currentVersion) <= 0) return null;
3430
- return { latestVersion: state.latestVersion };
3431
- } catch {
3432
- return null;
3433
- }
3434
- }
3435
- function scheduleBackgroundCheck(currentVersion) {
3436
- fetchLatestVersion().then((latestVersion) => {
3437
- const newState = {
3438
- lastCheckedAt: Date.now(),
3439
- latestVersion: latestVersion ?? void 0,
3440
- updatePending: false
3441
- };
3442
- if (latestVersion && compareVersions(latestVersion, currentVersion) > 0) {
3443
- newState.updatePending = true;
3444
- }
3445
- writeState(newState);
3446
- }).catch(() => {
3447
- });
3448
- }
3449
- var periodicTimer = null;
3450
- function startPeriodicUpdateCheck(currentVersion, onUpdate) {
3451
- if (periodicTimer) return;
3452
- periodicTimer = setInterval(() => {
3453
- fetchLatestVersion().then((latestVersion) => {
3454
- if (!latestVersion) return;
3455
- if (compareVersions(latestVersion, currentVersion) <= 0) return;
3456
- const info = detectInstallInfo();
3457
- if (!info.updateCommand) return;
3458
- writeState({
3459
- lastCheckedAt: Date.now(),
3460
- latestVersion,
3461
- updatePending: true
3462
- });
3463
- onUpdate(
3464
- `Nolan just pushed a fresh update \u2014 ${currentVersion} \u2192 ${latestVersion}! Restart ezboss to grab it (or run ${info.updateCommand} if you can't wait).`
3465
- );
3466
- stopPeriodicUpdateCheck();
3467
- }).catch(() => {
3468
- });
3469
- }, CHECK_INTERVAL_MS);
3470
- periodicTimer.unref();
3471
- }
3472
- function stopPeriodicUpdateCheck() {
3473
- if (periodicTimer) {
3474
- clearInterval(periodicTimer);
3475
- periodicTimer = null;
3476
- }
3477
- }
3016
+ var updater = createAutoUpdater({
3017
+ packageName: "@prestyj/boss",
3018
+ stateFilePath: () => path4.join(os2.homedir(), ".ezcoder", "boss", "update-state.json"),
3019
+ periodicMessage: ({ currentVersion, latestVersion, updateCommand }) => `Ken just pushed a fresh update \u2014 ${currentVersion} \u2192 ${latestVersion}! Restart ezboss to grab it (or run ${updateCommand} if you can't wait).`
3020
+ });
3021
+ var checkAndAutoUpdate = updater.checkAndAutoUpdate;
3022
+ var getPendingUpdate = updater.getPendingUpdate;
3023
+ var startPeriodicUpdateCheck = updater.startPeriodicUpdateCheck;
3024
+ var stopPeriodicUpdateCheck = updater.stopPeriodicUpdateCheck;
3478
3025
 
3479
3026
  // src/orchestrator-app.tsx
3480
3027
  var import_jsx_runtime15 = __toESM(require_jsx_runtime(), 1);
@@ -4098,10 +3645,12 @@ async function runServeSubcommand(argv) {
4098
3645
  process.exit(1);
4099
3646
  }
4100
3647
  const settings = await loadSettings();
4101
- const bossProvider = settings.bossProvider ?? "anthropic";
4102
- const bossModel = cliBossModel ?? settings.bossModel ?? "claude-opus-4-8";
4103
- const workerProvider = settings.workerProvider ?? "anthropic";
4104
- const workerModel = cliWorkerModel ?? settings.workerModel ?? "claude-sonnet-4-6";
3648
+ const { bossProvider, bossModel, workerProvider, workerModel } = await resolveBossAuth({
3649
+ bossProvider: settings.bossProvider ?? "anthropic",
3650
+ bossModel: cliBossModel ?? settings.bossModel ?? "claude-opus-4-8",
3651
+ workerProvider: settings.workerProvider ?? "anthropic",
3652
+ workerModel: cliWorkerModel ?? settings.workerModel ?? "claude-sonnet-4-6"
3653
+ });
4105
3654
  await runBossServeMode({
4106
3655
  bossProvider,
4107
3656
  bossModel,
@@ -4111,6 +3660,45 @@ async function runServeSubcommand(argv) {
4111
3660
  telegram: { botToken, userId }
4112
3661
  });
4113
3662
  }
3663
+ var ALL_PROVIDERS = [
3664
+ "anthropic",
3665
+ "openai",
3666
+ "xiaomi",
3667
+ "gemini",
3668
+ "glm",
3669
+ "moonshot",
3670
+ "minimax",
3671
+ "deepseek",
3672
+ "openrouter"
3673
+ ];
3674
+ function bossDefaultModel(provider) {
3675
+ return provider === "anthropic" ? "claude-opus-4-8" : getDefaultModel(provider).id;
3676
+ }
3677
+ async function resolveBossAuth(input) {
3678
+ const auth = new AuthStorage();
3679
+ await auth.load();
3680
+ const stored = await auth.listProviders();
3681
+ const loggedIn = ALL_PROVIDERS.filter((p) => stored.includes(p));
3682
+ if (loggedIn.length === 0) {
3683
+ throw new Error('Not logged in to any provider. Run "ezcoder login" to authenticate.');
3684
+ }
3685
+ const fallback = loggedIn[0];
3686
+ const resolve = (preferredProvider, preferredModel, defaultFor) => {
3687
+ const provider = loggedIn.includes(preferredProvider) ? preferredProvider : fallback;
3688
+ const modelFits = getModel(preferredModel)?.provider === provider;
3689
+ return { provider, model: modelFits ? preferredModel : defaultFor(provider) };
3690
+ };
3691
+ const boss = resolve(input.bossProvider, input.bossModel, bossDefaultModel);
3692
+ const worker = resolve(input.workerProvider, input.workerModel, (p) => getDefaultModel(p).id);
3693
+ const fellBack = boss.provider !== input.bossProvider || worker.provider !== input.workerProvider;
3694
+ return {
3695
+ bossProvider: boss.provider,
3696
+ bossModel: boss.model,
3697
+ workerProvider: worker.provider,
3698
+ workerModel: worker.model,
3699
+ fellBack
3700
+ };
3701
+ }
4114
3702
  async function runOrchestrator(args) {
4115
3703
  if (args.projects.length === 0) {
4116
3704
  const links = await loadLinks();
@@ -4127,10 +3715,30 @@ async function runOrchestrator(args) {
4127
3715
  caption: `Spinning up ${args.projects.length} worker${args.projects.length === 1 ? "" : "s"}\u2026`
4128
3716
  });
4129
3717
  const settings = await loadSettings();
4130
- const finalBossProvider = args.bossProvider ?? settings.bossProvider ?? "anthropic";
4131
- const finalBossModel = args.bossModel ?? settings.bossModel ?? "claude-opus-4-8";
4132
- const finalWorkerProvider = args.workerProvider ?? settings.workerProvider ?? "anthropic";
4133
- const finalWorkerModel = args.workerModel ?? settings.workerModel ?? "claude-sonnet-4-6";
3718
+ const preferredBossProvider = args.bossProvider ?? settings.bossProvider ?? "anthropic";
3719
+ const preferredBossModel = args.bossModel ?? settings.bossModel ?? "claude-opus-4-8";
3720
+ const preferredWorkerProvider = args.workerProvider ?? settings.workerProvider ?? "anthropic";
3721
+ const preferredWorkerModel = args.workerModel ?? settings.workerModel ?? "claude-sonnet-4-6";
3722
+ const {
3723
+ bossProvider: finalBossProvider,
3724
+ bossModel: finalBossModel,
3725
+ workerProvider: finalWorkerProvider,
3726
+ workerModel: finalWorkerModel,
3727
+ fellBack
3728
+ } = await resolveBossAuth({
3729
+ bossProvider: preferredBossProvider,
3730
+ bossModel: preferredBossModel,
3731
+ workerProvider: preferredWorkerProvider,
3732
+ workerModel: preferredWorkerModel
3733
+ });
3734
+ if (fellBack) {
3735
+ log("INFO", "cli", "provider fallback", {
3736
+ preferredBoss: preferredBossProvider,
3737
+ boss: finalBossProvider,
3738
+ preferredWorker: preferredWorkerProvider,
3739
+ worker: finalWorkerProvider
3740
+ });
3741
+ }
4134
3742
  initLogger({
4135
3743
  version: VERSION,
4136
3744
  bossProvider: finalBossProvider,