thepopebot 1.2.76-beta.2 → 1.2.76-beta.21

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 (128) hide show
  1. package/README.md +3 -3
  2. package/api/CLAUDE.md +11 -4
  3. package/api/index.js +56 -18
  4. package/bin/CLAUDE.md +7 -4
  5. package/bin/cli.js +25 -45
  6. package/config/CLAUDE.md +23 -4
  7. package/drizzle/0021_coding_agent_workspace.sql +1 -0
  8. package/drizzle/0022_organic_apocalypse.sql +16 -0
  9. package/drizzle/0023_needy_ender_wiggin.sql +1 -0
  10. package/drizzle/meta/0021_snapshot.json +639 -0
  11. package/drizzle/meta/0022_snapshot.json +743 -0
  12. package/drizzle/meta/0023_snapshot.json +750 -0
  13. package/drizzle/meta/_journal.json +21 -0
  14. package/lib/CLAUDE.md +2 -2
  15. package/lib/actions.js +9 -1
  16. package/lib/ai/CLAUDE.md +72 -57
  17. package/lib/ai/helper-llm.js +108 -0
  18. package/lib/ai/index.js +308 -438
  19. package/lib/ai/line-mappers.js +42 -24
  20. package/lib/ai/scope.js +26 -0
  21. package/lib/ai/sdk-adapters/CLAUDE.md +114 -0
  22. package/lib/ai/sdk-adapters/claude-code.js +120 -8
  23. package/lib/ai/system-prompt.js +34 -0
  24. package/lib/ai/workspace-setup.js +19 -35
  25. package/lib/channels/CLAUDE.md +14 -4
  26. package/lib/channels/base.js +6 -2
  27. package/lib/channels/commands/index.js +42 -0
  28. package/lib/channels/commands/session.js +53 -0
  29. package/lib/channels/commands/verify.js +18 -0
  30. package/lib/channels/telegram.js +79 -28
  31. package/lib/chat/CLAUDE.md +4 -4
  32. package/lib/chat/actions.js +270 -49
  33. package/lib/chat/api.js +185 -31
  34. package/lib/chat/components/CLAUDE.md +6 -2
  35. package/lib/chat/components/chat-input.js +77 -47
  36. package/lib/chat/components/chat-input.jsx +77 -40
  37. package/lib/chat/components/chat-page.js +2 -0
  38. package/lib/chat/components/chat-page.jsx +3 -0
  39. package/lib/chat/components/chat.js +62 -14
  40. package/lib/chat/components/chat.jsx +68 -10
  41. package/lib/chat/components/code-mode-toggle.js +141 -22
  42. package/lib/chat/components/code-mode-toggle.jsx +129 -20
  43. package/lib/chat/components/containers-page.js +58 -40
  44. package/lib/chat/components/containers-page.jsx +64 -25
  45. package/lib/chat/components/crons-page.js +17 -3
  46. package/lib/chat/components/crons-page.jsx +34 -6
  47. package/lib/chat/components/index.js +2 -2
  48. package/lib/chat/components/message.js +18 -3
  49. package/lib/chat/components/message.jsx +18 -3
  50. package/lib/chat/components/profile-page.js +182 -4
  51. package/lib/chat/components/profile-page.jsx +196 -1
  52. package/lib/chat/components/scope-picker.js +21 -0
  53. package/lib/chat/components/scope-picker.jsx +27 -0
  54. package/lib/chat/components/settings-chat-page.js +11 -11
  55. package/lib/chat/components/settings-chat-page.jsx +14 -18
  56. package/lib/chat/components/settings-coding-agents-page.js +110 -16
  57. package/lib/chat/components/settings-coding-agents-page.jsx +87 -3
  58. package/lib/chat/components/settings-github-page.js +5 -0
  59. package/lib/chat/components/settings-github-page.jsx +5 -0
  60. package/lib/chat/components/settings-layout.js +3 -3
  61. package/lib/chat/components/settings-layout.jsx +3 -3
  62. package/lib/chat/components/settings-secrets-layout.js +1 -2
  63. package/lib/chat/components/settings-secrets-layout.jsx +1 -2
  64. package/lib/chat/components/settings-secrets-page.js +180 -75
  65. package/lib/chat/components/settings-secrets-page.jsx +212 -66
  66. package/lib/chat/components/triggers-page.js +17 -3
  67. package/lib/chat/components/triggers-page.jsx +34 -6
  68. package/lib/chat/components/ui/combobox.js +18 -2
  69. package/lib/chat/components/ui/combobox.jsx +17 -1
  70. package/lib/chat/components/ui/dropdown-menu.js +23 -2
  71. package/lib/chat/components/ui/dropdown-menu.jsx +27 -2
  72. package/lib/chat/telegram-profile.js +33 -0
  73. package/lib/cluster/CLAUDE.md +9 -3
  74. package/lib/code/CLAUDE.md +11 -3
  75. package/lib/code/actions.js +47 -8
  76. package/lib/code/terminal-view.js +31 -21
  77. package/lib/code/terminal-view.jsx +32 -23
  78. package/lib/config.js +15 -4
  79. package/lib/containers/CLAUDE.md +16 -6
  80. package/lib/db/CLAUDE.md +5 -2
  81. package/lib/db/chats.js +9 -17
  82. package/lib/db/code-workspaces.js +8 -3
  83. package/lib/db/config.js +0 -1
  84. package/lib/db/index.js +12 -0
  85. package/lib/db/schema.js +24 -1
  86. package/lib/db/user-channels.js +129 -0
  87. package/lib/llm-providers.js +8 -0
  88. package/lib/maintenance.js +31 -21
  89. package/lib/tools/CLAUDE.md +12 -3
  90. package/lib/tools/assemblyai.js +17 -0
  91. package/lib/tools/create-agent-job.js +12 -8
  92. package/lib/tools/docker.js +34 -10
  93. package/lib/tools/github.js +34 -0
  94. package/lib/tools/telegram.js +106 -0
  95. package/lib/utils/render-md.js +44 -18
  96. package/package.json +8 -8
  97. package/setup/CLAUDE.md +11 -5
  98. package/setup/lib/providers.mjs +2 -1
  99. package/setup/lib/targets.mjs +13 -16
  100. package/setup/lib/telegram.mjs +8 -69
  101. package/templates/.env.example +0 -7
  102. package/templates/.github/workflows/rebuild-event-handler.yml +1 -1
  103. package/templates/.gitignore.template +1 -3
  104. package/templates/CLAUDE.md +1 -1
  105. package/templates/CLAUDE.md.template +29 -7
  106. package/templates/agent-job/CLAUDE.md.template +5 -3
  107. package/templates/agent-job/CRONS.json +16 -0
  108. package/templates/agent-job/SYSTEM.md +16 -11
  109. package/templates/agents/CLAUDE.md.template +17 -17
  110. package/templates/coding-workspace/CLAUDE.md.template +7 -0
  111. package/templates/data/CLAUDE.md.template +1 -1
  112. package/templates/docker-compose.custom.yml +1 -0
  113. package/templates/docker-compose.yml +1 -0
  114. package/templates/event-handler/CLAUDE.md.template +79 -0
  115. package/templates/event-handler/TRIGGERS.json +18 -2
  116. package/templates/skills/CLAUDE.md.template +20 -22
  117. package/templates/skills/{library/agent-job-secrets → agent-job-secrets}/SKILL.md +2 -2
  118. package/lib/ai/agent.js +0 -65
  119. package/lib/ai/async-channel.js +0 -51
  120. package/lib/ai/model.js +0 -130
  121. package/lib/ai/tools.js +0 -164
  122. package/lib/tools/openai.js +0 -37
  123. package/setup/lib/telegram-verify.mjs +0 -63
  124. package/setup/setup-telegram.mjs +0 -260
  125. package/templates/agent-job/SOUL.md +0 -17
  126. /package/templates/{skills/active/.gitkeep → coding-workspace/SYSTEM.md} +0 -0
  127. /package/templates/skills/{library/agent-job-secrets → agent-job-secrets}/agent-job-secrets.js +0 -0
  128. /package/templates/skills/{library/playwright-cli → playwright-cli}/SKILL.md +0 -0
@@ -7,7 +7,6 @@ import {
7
7
  getChatByWorkspaceId,
8
8
  getMessagesByChatId,
9
9
  deleteChat as dbDeleteChat,
10
- deleteAllChatsByUser,
11
10
  updateChatTitle,
12
11
  toggleChatStarred,
13
12
  } from '../db/chats.js';
@@ -139,15 +138,6 @@ export async function starChat(chatId) {
139
138
  return { success: true, starred };
140
139
  }
141
140
 
142
- /**
143
- * Delete all chats for the authenticated user.
144
- * @returns {Promise<{success: boolean}>}
145
- */
146
- export async function deleteAllChats() {
147
- const user = await requireAuth();
148
- deleteAllChatsByUser(user.id);
149
- return { success: true };
150
- }
151
141
 
152
142
  /**
153
143
  * Get notifications, newest first, with pagination.
@@ -666,6 +656,10 @@ export async function getCodingAgentSettings() {
666
656
  const kimiCliEnabled = getConfig('CODING_AGENT_KIMI_CLI_ENABLED');
667
657
  const kimiCliProvider = getConfig('CODING_AGENT_KIMI_CLI_PROVIDER') || '';
668
658
  const kimiCliModel = getConfig('CODING_AGENT_KIMI_CLI_MODEL') || '';
659
+ const agentModeBranch = getConfig('AGENT_MODE_BRANCH');
660
+ const codeModeBranch = getConfig('CODE_MODE_BRANCH');
661
+ const agentModeGitAction = getConfig('AGENT_MODE_GIT_ACTION');
662
+ const codeModeGitAction = getConfig('CODE_MODE_GIT_ACTION');
669
663
 
670
664
  // Credential readiness
671
665
  const oauthTokenCount = getOAuthTokenCount('claudeCode');
@@ -712,6 +706,10 @@ export async function getCodingAgentSettings() {
712
706
  provider: kimiCliProvider,
713
707
  model: kimiCliModel,
714
708
  },
709
+ modeDefaults: {
710
+ agent: { branch: agentModeBranch, gitAction: agentModeGitAction },
711
+ code: { branch: codeModeBranch, gitAction: codeModeGitAction },
712
+ },
715
713
  builtinProviders: BUILTIN_PROVIDERS,
716
714
  credentialStatuses,
717
715
  customProviders,
@@ -722,6 +720,66 @@ export async function getCodingAgentSettings() {
722
720
  }
723
721
  }
724
722
 
723
+ /**
724
+ * Return the list of coding agents that are enabled and have valid credentials.
725
+ * Used to populate the right-click agent picker on the Interactive toggle.
726
+ * @returns {Promise<Array<{value: string, label: string}>>}
727
+ */
728
+ export async function getAvailableCodingAgents() {
729
+ await requireAuth();
730
+ try {
731
+ const settings = await getCodingAgentSettings();
732
+ if (settings.error) return [];
733
+
734
+ const statusMap = new Map((settings.credentialStatuses || []).map(s => [s.key, s.isSet]));
735
+
736
+ function isProviderReady(provider) {
737
+ const builtin = settings.builtinProviders?.[provider];
738
+ if (builtin?.credentialKey) return statusMap.get(builtin.credentialKey) || false;
739
+ // Custom providers store their own credentials — if selected they're ready
740
+ const custom = (settings.customProviders || []).find(p => p.id === provider);
741
+ return !!custom;
742
+ }
743
+
744
+ const available = [];
745
+
746
+ if (settings.claudeCode?.enabled) {
747
+ const { claudeCode } = settings;
748
+ const backend = claudeCode.backend || 'anthropic';
749
+ const ready = backend === 'anthropic'
750
+ ? (claudeCode.auth === 'oauth' ? claudeCode.oauthTokenCount > 0 : claudeCode.anthropicKeySet)
751
+ : isProviderReady(backend);
752
+ if (ready) available.push({ value: 'claude-code', label: 'Claude Code' });
753
+ }
754
+
755
+ if (settings.pi?.enabled && settings.pi?.provider && isProviderReady(settings.pi.provider)) {
756
+ available.push({ value: 'pi-coding-agent', label: 'Pi' });
757
+ }
758
+
759
+ if (settings.geminiCli?.enabled && settings.geminiCli?.googleKeySet) {
760
+ available.push({ value: 'gemini-cli', label: 'Gemini CLI' });
761
+ }
762
+
763
+ if (settings.codexCli?.enabled) {
764
+ const { codexCli } = settings;
765
+ const ready = codexCli.auth === 'oauth' ? codexCli.oauthTokenCount > 0 : codexCli.codexKeySet;
766
+ if (ready) available.push({ value: 'codex-cli', label: 'Codex CLI' });
767
+ }
768
+
769
+ if (settings.openCode?.enabled && settings.openCode?.provider && isProviderReady(settings.openCode.provider)) {
770
+ available.push({ value: 'opencode', label: 'OpenCode' });
771
+ }
772
+
773
+ if (settings.kimiCli?.enabled && settings.kimiCli?.provider && isProviderReady(settings.kimiCli.provider)) {
774
+ available.push({ value: 'kimi-cli', label: 'Kimi CLI' });
775
+ }
776
+
777
+ return available;
778
+ } catch {
779
+ return [];
780
+ }
781
+ }
782
+
725
783
  /**
726
784
  * Update per-agent coding agent config.
727
785
  * @param {string} agent - 'claude-code' or 'pi-coding-agent'
@@ -790,6 +848,54 @@ export async function setCodingAgentDefault(agent) {
790
848
  }
791
849
  }
792
850
 
851
+ const MODE_BRANCH_VALUES = new Set(['default', 'dynamic']);
852
+ const MODE_GIT_ACTION_VALUES = new Set(['commit', 'push', 'create-pr', 'pull']);
853
+
854
+ /**
855
+ * Get the admin-configured default git action for a chat mode.
856
+ * Used by the chat UI to seed the workspace command dropdown.
857
+ * @param {'agent'|'code'} mode
858
+ */
859
+ export async function getModeGitActionDefault(mode) {
860
+ await requireAuth();
861
+ if (mode !== 'agent' && mode !== 'code') return null;
862
+ const { getConfig } = await import('../config.js');
863
+ return getConfig(mode === 'code' ? 'CODE_MODE_GIT_ACTION' : 'AGENT_MODE_GIT_ACTION') || null;
864
+ }
865
+
866
+ /**
867
+ * Set a per-mode default (branch behavior or git action).
868
+ * @param {'agent'|'code'} mode
869
+ * @param {'branch'|'gitAction'} field
870
+ * @param {string} value
871
+ */
872
+ export async function setModeDefault(mode, field, value) {
873
+ await requireAuth();
874
+ try {
875
+ if (mode !== 'agent' && mode !== 'code') return { error: 'Invalid mode' };
876
+ if (field === 'branch' && !MODE_BRANCH_VALUES.has(value)) return { error: 'Invalid branch value' };
877
+ if (field === 'gitAction' && !MODE_GIT_ACTION_VALUES.has(value)) return { error: 'Invalid git action value' };
878
+
879
+ const keyMap = {
880
+ 'agent.branch': 'AGENT_MODE_BRANCH',
881
+ 'code.branch': 'CODE_MODE_BRANCH',
882
+ 'agent.gitAction': 'AGENT_MODE_GIT_ACTION',
883
+ 'code.gitAction': 'CODE_MODE_GIT_ACTION',
884
+ };
885
+ const key = keyMap[`${mode}.${field}`];
886
+ if (!key) return { error: 'Invalid field' };
887
+
888
+ const { setConfigValue } = await import('../db/config.js');
889
+ const { invalidateConfigCache } = await import('../config.js');
890
+ setConfigValue(key, value);
891
+ invalidateConfigCache();
892
+ return { success: true };
893
+ } catch (err) {
894
+ console.error('Failed to set mode default:', err);
895
+ return { error: 'Failed to set mode default' };
896
+ }
897
+ }
898
+
793
899
  // ─────────────────────────────────────────────────────────────────────────────
794
900
  // Settings — General sub-tab
795
901
  // ─────────────────────────────────────────────────────────────────────────────
@@ -846,7 +952,6 @@ const API_KEY_SECRETS = new Set([
846
952
  'TELEGRAM_WEBHOOK_SECRET',
847
953
  'ASSEMBLYAI_API_KEY',
848
954
  ]);
849
- const API_KEY_CONFIGS = new Set(['TELEGRAM_CHAT_ID']);
850
955
 
851
956
  /**
852
957
  * Get settings for the API Keys sub-tab.
@@ -854,11 +959,9 @@ const API_KEY_CONFIGS = new Set(['TELEGRAM_CHAT_ID']);
854
959
  export async function getApiKeySettings() {
855
960
  await requireAuth();
856
961
  try {
857
- const { getSecretStatus, getConfigValue } = await import('../db/config.js');
858
- const secretKeys = [...API_KEY_SECRETS];
859
- const statuses = getSecretStatus(secretKeys);
860
- const telegramChatId = getConfigValue('TELEGRAM_CHAT_ID');
861
- return { secrets: statuses, telegramChatId };
962
+ const { getSecretStatus } = await import('../db/config.js');
963
+ const statuses = getSecretStatus([...API_KEY_SECRETS]);
964
+ return { secrets: statuses };
862
965
  } catch (err) {
863
966
  console.error('Failed to get API key settings:', err);
864
967
  return { error: 'Failed to load settings' };
@@ -870,20 +973,17 @@ export async function getApiKeySettings() {
870
973
  */
871
974
  export async function updateApiKeySetting(key, value) {
872
975
  const user = await requireAuth();
976
+ if (!API_KEY_SECRETS.has(key)) {
977
+ return { error: 'Invalid key' };
978
+ }
873
979
  try {
874
- const { setConfigSecret, setConfigValue, deleteConfigSecret } = await import('../db/config.js');
980
+ const { setConfigSecret, deleteConfigSecret } = await import('../db/config.js');
875
981
  const { invalidateConfigCache } = await import('../config.js');
876
982
 
877
- if (API_KEY_SECRETS.has(key)) {
878
- if (value) {
879
- setConfigSecret(key, value, user.id);
880
- } else {
881
- deleteConfigSecret(key);
882
- }
883
- } else if (API_KEY_CONFIGS.has(key)) {
884
- setConfigValue(key, value, user.id);
983
+ if (value) {
984
+ setConfigSecret(key, value, user.id);
885
985
  } else {
886
- return { error: 'Invalid key' };
986
+ deleteConfigSecret(key);
887
987
  }
888
988
 
889
989
  invalidateConfigCache();
@@ -916,12 +1016,114 @@ export async function regenerateWebhookSecret(key) {
916
1016
  }
917
1017
  }
918
1018
 
1019
+ // ─────────────────────────────────────────────────────────────────────────────
1020
+ // Settings — Telegram setup flow
1021
+ // ─────────────────────────────────────────────────────────────────────────────
1022
+
1023
+ /**
1024
+ * Get Telegram status: bot info (if token valid) and webhook info.
1025
+ * Used by the admin UI to render the current state of each setup step.
1026
+ */
1027
+ export async function getTelegramStatus() {
1028
+ await requireAuth();
1029
+ try {
1030
+ const { getConfigSecret } = await import('../db/config.js');
1031
+ const { validateBotToken, getTelegramWebhookInfo } = await import('../tools/telegram.js');
1032
+
1033
+ const botToken = getConfigSecret('TELEGRAM_BOT_TOKEN');
1034
+ const webhookSecret = getConfigSecret('TELEGRAM_WEBHOOK_SECRET');
1035
+
1036
+ let botInfo = null;
1037
+ let webhookInfo = null;
1038
+ if (botToken) {
1039
+ const v = await validateBotToken(botToken);
1040
+ if (v.valid) botInfo = { username: v.botInfo.username, id: v.botInfo.id };
1041
+ try {
1042
+ const info = await getTelegramWebhookInfo(botToken);
1043
+ if (info.ok) {
1044
+ webhookInfo = {
1045
+ url: info.result.url || null,
1046
+ hasCustomCertificate: info.result.has_custom_certificate,
1047
+ pendingUpdates: info.result.pending_update_count,
1048
+ lastErrorMessage: info.result.last_error_message || null,
1049
+ };
1050
+ }
1051
+ } catch {
1052
+ // ignore — webhook info is best-effort
1053
+ }
1054
+ }
1055
+
1056
+ return {
1057
+ botInfo,
1058
+ botTokenSet: !!botToken,
1059
+ webhookSecretSet: !!webhookSecret,
1060
+ webhookInfo,
1061
+ };
1062
+ } catch (err) {
1063
+ console.error('Failed to get Telegram status:', err);
1064
+ return { error: 'Failed to load Telegram status' };
1065
+ }
1066
+ }
1067
+
1068
+ /**
1069
+ * Validate a Telegram bot token without saving it.
1070
+ * Called from the UI as the user pastes a token to show live feedback.
1071
+ */
1072
+ export async function validateTelegramToken(token) {
1073
+ await requireAuth();
1074
+ if (!token) return { valid: false, error: 'Token is required' };
1075
+ try {
1076
+ const { validateBotToken } = await import('../tools/telegram.js');
1077
+ const result = await validateBotToken(token);
1078
+ if (result.valid) {
1079
+ return { valid: true, botInfo: { username: result.botInfo.username, id: result.botInfo.id } };
1080
+ }
1081
+ return { valid: false, error: result.error };
1082
+ } catch (err) {
1083
+ return { valid: false, error: err.message };
1084
+ }
1085
+ }
1086
+
1087
+ /**
1088
+ * Register the Telegram webhook with the currently saved bot token.
1089
+ * Generates a fresh webhook secret, saves it, and calls Telegram's setWebhook.
1090
+ * APP_URL must be configured.
1091
+ */
1092
+ export async function registerTelegramWebhook() {
1093
+ const user = await requireAuth();
1094
+ try {
1095
+ const { getConfigSecret, setConfigSecret } = await import('../db/config.js');
1096
+ const { invalidateConfigCache, getConfig } = await import('../config.js');
1097
+ const { setTelegramWebhook, generateWebhookSecret } = await import('../tools/telegram.js');
1098
+
1099
+ const botToken = getConfigSecret('TELEGRAM_BOT_TOKEN');
1100
+ if (!botToken) return { error: 'Bot token must be set first' };
1101
+
1102
+ const appUrl = getConfig('APP_URL');
1103
+ if (!appUrl) return { error: 'APP_URL must be configured first' };
1104
+
1105
+ const webhookUrl = `${appUrl.replace(/\/$/, '')}/api/telegram/webhook`;
1106
+ const secret = generateWebhookSecret();
1107
+ setConfigSecret('TELEGRAM_WEBHOOK_SECRET', secret, user.id);
1108
+ invalidateConfigCache();
1109
+
1110
+ const result = await setTelegramWebhook(botToken, webhookUrl, secret);
1111
+ if (!result.ok) {
1112
+ return { error: result.description || 'Failed to register webhook' };
1113
+ }
1114
+ return { success: true, webhookUrl };
1115
+ } catch (err) {
1116
+ console.error('Failed to register Telegram webhook:', err);
1117
+ return { error: err.message };
1118
+ }
1119
+ }
1120
+
919
1121
  // ─────────────────────────────────────────────────────────────────────────────
920
1122
  // Settings — Chat sub-tab
921
1123
  // ─────────────────────────────────────────────────────────────────────────────
922
1124
 
923
1125
  /**
924
- * Get settings for the Chat sub-tab.
1126
+ * Get settings for the Helper LLM page (and Providers page — both share this).
925
1127
  */
926
1128
  export async function getChatSettings() {
927
1129
  await requireAuth();
@@ -936,34 +1138,16 @@ export async function getChatSettings() {
936
1138
  // Get custom providers (masked)
937
1139
  const customProviders = getCustomProviders();
938
1140
 
939
- // Get active config values
1141
+ // Get active helper LLM config
940
1142
  const activeProvider = getConfigValue('LLM_PROVIDER') || '';
941
1143
  const activeModel = getConfigValue('LLM_MODEL') || '';
942
1144
  const maxTokens = getConfigValue('LLM_MAX_TOKENS') || '4096';
943
1145
  const agentBackend = getConfigValue('AGENT_BACKEND') || '';
944
1146
 
945
- // Check if the current coding agent has an in-process SDK adapter
946
- const { getConfig } = await import('../config.js');
947
- const { getSdkAdapter } = await import('../ai/sdk-adapters/index.js');
948
- const codingAgent = getConfig('CODING_AGENT');
949
- const sdkAgentActive = !!getSdkAdapter(codingAgent);
950
-
951
- // Human-readable agent names
952
- const agentNames = {
953
- 'claude-code': 'Claude Code',
954
- 'pi-coding-agent': 'Pi Coding Agent',
955
- 'gemini-cli': 'Gemini CLI',
956
- 'codex-cli': 'Codex CLI',
957
- 'opencode': 'OpenCode',
958
- 'kimi-cli': 'Kimi CLI',
959
- };
960
-
961
1147
  return {
962
1148
  builtinProviders: BUILTIN_PROVIDERS,
963
1149
  credentialStatuses,
964
1150
  customProviders,
965
- sdkAgentActive,
966
- defaultAgent: agentNames[codingAgent] || codingAgent,
967
1151
  active: {
968
1152
  provider: activeProvider,
969
1153
  model: activeModel,
@@ -972,8 +1156,8 @@ export async function getChatSettings() {
972
1156
  },
973
1157
  };
974
1158
  } catch (err) {
975
- console.error('Failed to get chat settings:', err);
976
- return { error: 'Failed to load chat settings' };
1159
+ console.error('Failed to get helper LLM settings:', err);
1160
+ return { error: 'Failed to load helper LLM settings' };
977
1161
  }
978
1162
  }
979
1163
 
@@ -1189,6 +1373,7 @@ export async function getGitHubConfig() {
1189
1373
  }
1190
1374
 
1191
1375
  // Build variables list: known names + any extras, with current value
1376
+ const variablesError = !Array.isArray(remoteVariables) ? (remoteVariables?.error || 'Failed to load variables') : null;
1192
1377
  const remoteVarMap = new Map(
1193
1378
  Array.isArray(remoteVariables) ? remoteVariables.map((v) => [v.name, v.value]) : []
1194
1379
  );
@@ -1199,7 +1384,7 @@ export async function getGitHubConfig() {
1199
1384
  }
1200
1385
  }
1201
1386
 
1202
- return { secrets, variables };
1387
+ return { secrets, variables, variablesError };
1203
1388
  }
1204
1389
 
1205
1390
  /**
@@ -1348,3 +1533,39 @@ export async function deleteGitHubVariableAction(name) {
1348
1533
  }
1349
1534
  }
1350
1535
 
1536
+ /**
1537
+ * Issue (or re-issue) a Telegram verification code for the current user.
1538
+ * Returns the code, expiry, and the bot's @username for instructions.
1539
+ */
1540
+ export async function issueTelegramCode() {
1541
+ const user = await requireAuth();
1542
+ const { issueCode } = await import('../db/user-channels.js');
1543
+ const { getConfig } = await import('../config.js');
1544
+ const { validateBotToken } = await import('../tools/telegram.js');
1545
+
1546
+ try {
1547
+ const row = issueCode(user.id, 'telegram');
1548
+ const botToken = getConfig('TELEGRAM_BOT_TOKEN');
1549
+ let botUsername = null;
1550
+ if (botToken) {
1551
+ const info = await validateBotToken(botToken);
1552
+ if (info.valid) botUsername = info.botInfo.username;
1553
+ }
1554
+ return { code: row.code, expiresAt: row.codeExpiresAt, botUsername };
1555
+ } catch (err) {
1556
+ // If already verified, surface that so the UI can offer "unlink first"
1557
+ return { error: err.message };
1558
+ }
1559
+ }
1560
+
1561
+ /**
1562
+ * Unlink the current user's Telegram channel binding.
1563
+ */
1564
+ export async function unlinkTelegramChannel() {
1565
+ const user = await requireAuth();
1566
+ const { unlink } = await import('../db/user-channels.js');
1567
+ unlink(user.id, 'telegram');
1568
+ return { ok: true };
1569
+ }
1570
+
1571
+