@foxden-app/foxclaw 0.7.3 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/.env.example +22 -8
  2. package/CHANGELOG.md +134 -0
  3. package/README.md +21 -1
  4. package/README_EN.md +21 -1
  5. package/dist/antigravity/adapter.d.ts +18 -0
  6. package/dist/antigravity/adapter.js +394 -0
  7. package/dist/antigravity/auth.d.ts +126 -0
  8. package/dist/antigravity/auth.js +933 -0
  9. package/dist/antigravity/client.d.ts +32 -0
  10. package/dist/antigravity/client.js +244 -0
  11. package/dist/antigravity/controller.d.ts +101 -0
  12. package/dist/antigravity/controller.js +2007 -0
  13. package/dist/antigravity/conversations.d.ts +29 -0
  14. package/dist/antigravity/conversations.js +289 -0
  15. package/dist/antigravity/events.d.ts +102 -0
  16. package/dist/antigravity/events.js +143 -0
  17. package/dist/antigravity/runtime.d.ts +49 -0
  18. package/dist/antigravity/runtime.js +82 -0
  19. package/dist/antigravity/subagents.d.ts +66 -0
  20. package/dist/antigravity/subagents.js +459 -0
  21. package/dist/channels/telegram/telegram_channel_adapter.d.ts +10 -3
  22. package/dist/channels/telegram/telegram_channel_adapter.js +2 -1
  23. package/dist/channels/weixin/weixin_channel_adapter.d.ts +5 -1
  24. package/dist/codex_app/adapter.d.ts +20 -0
  25. package/dist/codex_app/adapter.js +213 -0
  26. package/dist/codex_app/client.d.ts +1 -1
  27. package/dist/codex_app/client.js +12 -0
  28. package/dist/config.d.ts +14 -0
  29. package/dist/config.js +65 -16
  30. package/dist/controller/controller.d.ts +12 -5
  31. package/dist/controller/controller.js +109 -8
  32. package/dist/core/attachments.d.ts +11 -0
  33. package/dist/core/attachments.js +56 -0
  34. package/dist/core/engine_spi.d.ts +77 -0
  35. package/dist/core/engine_spi.js +1 -0
  36. package/dist/core/orchestrator.d.ts +106 -0
  37. package/dist/core/orchestrator.js +1556 -0
  38. package/dist/core/stream_preview.d.ts +29 -0
  39. package/dist/core/stream_preview.js +146 -0
  40. package/dist/core/turn_queue.d.ts +19 -0
  41. package/dist/core/turn_queue.js +41 -0
  42. package/dist/i18n.d.ts +6 -0
  43. package/dist/i18n.js +49 -0
  44. package/dist/main.js +375 -446
  45. package/dist/opencode/adapter.d.ts +11 -0
  46. package/dist/opencode/adapter.js +169 -0
  47. package/dist/opencode/controller.js +2 -2
  48. package/dist/store/database.d.ts +44 -0
  49. package/dist/store/database.js +275 -43
  50. package/dist/store/token_usage.d.ts +23 -0
  51. package/dist/store/token_usage.js +61 -0
  52. package/dist/telegram/api.d.ts +1 -0
  53. package/dist/telegram/api.js +80 -3
  54. package/dist/telegram/bot_home.js +5 -4
  55. package/dist/telegram/gateway.d.ts +4 -0
  56. package/dist/telegram/gateway.js +11 -0
  57. package/dist/types.d.ts +3 -0
  58. package/dist/update.d.ts +6 -0
  59. package/dist/update.js +95 -7
  60. package/dist/voice/tts.js +4 -2
  61. package/docs/devlogs//346/212/200/346/234/257/351/200/232/350/256/257/02./347/273/237/344/270/200/351/200/232/351/201/223/350/260/203/345/272/246/345/231/250/344/270/216/345/217/257/346/217/222/346/213/224/345/274/225/346/223/216SPI/357/274/232FoxClaw/344/273/216/345/215/225/344/275/223/350/265/260/345/220/221/345/244/232Agent/345/272/225/345/272/247.md +192 -0
  62. package/docs/devlogs//347/254/254/344/270/200/344/272/272/347/247/260/347/211/210/02./347/257/207/344/272/214_/345/275/223/344/270/200/344/270/252/346/241/245/346/216/245/345/231/250/345/274/200/345/247/213/345/255/246/344/274/232/345/220/254/346/207/202/346/211/200/346/234/211Agent.md +45 -0
  63. package/package.json +2 -2
@@ -0,0 +1,213 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import { normalizeTurnActivityEvent } from '../controller/activity.js';
3
+ import { buildAttachmentPrompt } from '../telegram/media.js';
4
+ export class CodexEngineAdapter {
5
+ client;
6
+ id;
7
+ name;
8
+ defaultModel;
9
+ defaultApprovalPolicy;
10
+ defaultSandboxMode;
11
+ constructor(client, options = {}) {
12
+ this.client = client;
13
+ this.id = options.id ?? 'codex';
14
+ this.name = options.name ?? 'OpenAI Codex (App Server)';
15
+ this.defaultModel = options.defaultModel;
16
+ this.defaultApprovalPolicy = options.defaultApprovalPolicy ?? 'on-request';
17
+ this.defaultSandboxMode = options.defaultSandboxMode ?? 'workspace-write';
18
+ }
19
+ async listModels() {
20
+ try {
21
+ const models = await this.client.listModels();
22
+ const list = models.map((m) => ({
23
+ id: m.id,
24
+ name: m.displayName || m.id,
25
+ description: m.description,
26
+ isDefault: m.isDefault || m.id === this.defaultModel,
27
+ }));
28
+ if (!list.some((m) => m.id === 'gpt-6-sol')) {
29
+ list.push({
30
+ id: 'gpt-6-sol',
31
+ name: 'GPT-6-Sol',
32
+ description: 'Next-generation flagship model with deep reasoning',
33
+ isDefault: false,
34
+ });
35
+ }
36
+ return list;
37
+ }
38
+ catch {
39
+ return [];
40
+ }
41
+ }
42
+ executeTurn(request) {
43
+ const emitter = new EventEmitter();
44
+ let threadId = request.threadId;
45
+ let turnId = null;
46
+ let cancelled = false;
47
+ let promptText = request.prompt;
48
+ if (request.stagedAttachments && request.stagedAttachments.length > 0 && !request.prompt.includes('Telegram attachments:')) {
49
+ promptText = buildAttachmentPrompt(request.prompt, request.stagedAttachments);
50
+ }
51
+ let accumulatedResponse = '';
52
+ let finalResult = null;
53
+ let turnError = null;
54
+ const resultWaiters = [];
55
+ const resolveWaiters = (res) => {
56
+ while (resultWaiters.length > 0) {
57
+ resultWaiters.shift()(res);
58
+ }
59
+ };
60
+ const onNotification = (msg) => {
61
+ try {
62
+ const ev = normalizeTurnActivityEvent(msg);
63
+ if (!ev)
64
+ return;
65
+ if (ev.turnId && turnId && ev.turnId !== turnId)
66
+ return;
67
+ if (ev.kind === 'agent_message_delta') {
68
+ accumulatedResponse += ev.delta;
69
+ emitter.emit('delta', ev.delta);
70
+ }
71
+ else if (ev.kind === 'tool_started') {
72
+ const cmdName = Array.isArray(ev.exec?.command) ? ev.exec.command.join(' ') : 'command';
73
+ emitter.emit('tool', {
74
+ name: cmdName,
75
+ status: 'running',
76
+ });
77
+ }
78
+ else if (ev.kind === 'tool_completed') {
79
+ const cmdName = Array.isArray(ev.exec?.command) ? ev.exec.command.join(' ') : 'command';
80
+ emitter.emit('tool', {
81
+ name: cmdName,
82
+ status: 'completed',
83
+ });
84
+ }
85
+ else if (ev.kind === 'turn_completed') {
86
+ cleanup();
87
+ finalResult = {
88
+ kind: 'result',
89
+ status: ev.state === 'interrupted' ? 'INTERRUPTED' : 'SUCCESS',
90
+ response: accumulatedResponse,
91
+ conversationId: threadId,
92
+ };
93
+ emitter.emit('result', finalResult);
94
+ resolveWaiters(finalResult);
95
+ }
96
+ }
97
+ catch {
98
+ // Ignore parsing errors for unrelated notifications
99
+ }
100
+ };
101
+ const cleanup = () => {
102
+ this.client.off('notification', onNotification);
103
+ };
104
+ this.client.on('notification', onNotification);
105
+ const effectiveModel = request.model && request.model !== 'default'
106
+ ? request.model
107
+ : (this.defaultModel && this.defaultModel !== 'default' ? this.defaultModel : null);
108
+ const run = async () => {
109
+ try {
110
+ if (!threadId) {
111
+ const session = await this.client.startThread({
112
+ cwd: request.cwd,
113
+ model: effectiveModel,
114
+ approvalPolicy: this.defaultApprovalPolicy,
115
+ sandboxMode: this.defaultSandboxMode,
116
+ });
117
+ threadId = session.thread.threadId;
118
+ }
119
+ else {
120
+ try {
121
+ await this.client.resumeThread({
122
+ threadId,
123
+ cwd: request.cwd,
124
+ approvalPolicy: this.defaultApprovalPolicy,
125
+ });
126
+ }
127
+ catch {
128
+ // thread might already be active on server
129
+ }
130
+ }
131
+ if (cancelled) {
132
+ cleanup();
133
+ return;
134
+ }
135
+ if (!threadId) {
136
+ throw new Error('Failed to resolve or create Codex thread for turn');
137
+ }
138
+ let turn;
139
+ try {
140
+ turn = await this.client.startTurn({
141
+ threadId,
142
+ input: [{ type: 'text', text: promptText, text_elements: [] }],
143
+ cwd: request.cwd,
144
+ model: effectiveModel,
145
+ effort: request.effort ?? null,
146
+ serviceTier: request.serviceTier ?? undefined,
147
+ collaborationMode: null,
148
+ approvalPolicy: this.defaultApprovalPolicy,
149
+ sandboxMode: this.defaultSandboxMode,
150
+ });
151
+ }
152
+ catch (turnErr) {
153
+ const errMsg = String(turnErr);
154
+ if (errMsg.includes('not found') ||
155
+ errMsg.includes('No thread') ||
156
+ errMsg.includes('stale') ||
157
+ errMsg.includes('invalid thread')) {
158
+ const session = await this.client.startThread({
159
+ cwd: request.cwd,
160
+ model: effectiveModel,
161
+ approvalPolicy: this.defaultApprovalPolicy,
162
+ sandboxMode: this.defaultSandboxMode,
163
+ });
164
+ threadId = session.thread.threadId;
165
+ turn = await this.client.startTurn({
166
+ threadId,
167
+ input: [{ type: 'text', text: promptText, text_elements: [] }],
168
+ cwd: request.cwd,
169
+ model: effectiveModel,
170
+ effort: request.effort ?? null,
171
+ serviceTier: request.serviceTier ?? undefined,
172
+ collaborationMode: null,
173
+ approvalPolicy: this.defaultApprovalPolicy,
174
+ sandboxMode: this.defaultSandboxMode,
175
+ });
176
+ }
177
+ else {
178
+ throw turnErr;
179
+ }
180
+ }
181
+ turnId = turn.id;
182
+ }
183
+ catch (err) {
184
+ cleanup();
185
+ turnError = err instanceof Error ? err : new Error(String(err));
186
+ emitter.emit('error', turnError);
187
+ resolveWaiters(null);
188
+ }
189
+ };
190
+ void run();
191
+ return {
192
+ turnId: turnId ?? undefined,
193
+ cancel: () => {
194
+ cancelled = true;
195
+ if (threadId && turnId) {
196
+ void this.client.interruptTurn(threadId, turnId).catch(() => { });
197
+ }
198
+ },
199
+ waitForResult: async () => {
200
+ if (finalResult)
201
+ return finalResult;
202
+ if (turnError)
203
+ return null;
204
+ return new Promise((resolve) => {
205
+ resultWaiters.push(resolve);
206
+ });
207
+ },
208
+ on: (event, listener) => {
209
+ emitter.on(event, listener);
210
+ },
211
+ };
212
+ }
213
+ }
@@ -49,7 +49,7 @@ interface StartTurnOptions {
49
49
  serviceTier?: string | null | undefined;
50
50
  collaborationMode: CodexCollaborationMode | null;
51
51
  }
52
- interface CodexAppServerRuntimeStatus {
52
+ export interface CodexAppServerRuntimeStatus {
53
53
  pid: number | null;
54
54
  port: number | null;
55
55
  running: boolean;
@@ -284,6 +284,18 @@ export class CodexAppClient extends EventEmitter {
284
284
  models.push(...rows.map(mapModel));
285
285
  cursor = typeof result.nextCursor === 'string' ? result.nextCursor : null;
286
286
  } while (cursor);
287
+ if (!models.some((m) => m.id === 'gpt-6-sol' || m.model === 'gpt-6-sol')) {
288
+ models.push({
289
+ id: 'gpt-6-sol',
290
+ model: 'gpt-6-sol',
291
+ displayName: 'GPT-6-Sol',
292
+ description: 'Next-generation flagship model with deep reasoning',
293
+ isDefault: false,
294
+ supportedReasoningEfforts: ['low', 'medium', 'high', 'xhigh', 'max'],
295
+ defaultReasoningEffort: 'medium',
296
+ serviceTiers: [],
297
+ });
298
+ }
287
299
  return models;
288
300
  }
289
301
  async readAccount(refreshToken = false) {
package/dist/config.d.ts CHANGED
@@ -9,7 +9,9 @@ export declare const DEFAULT_CODEX_APP_SERVER_STATE_PATH: string;
9
9
  export declare const DEFAULT_CODEX_APP_SERVER_LOG_PATH: string;
10
10
  export declare const DEFAULT_OPENCODE_SERVER_STATE_PATH: string;
11
11
  export declare const DEFAULT_OPENCODE_SERVER_LOG_PATH: string;
12
+ export declare const DEFAULT_ANTIGRAVITY_AUTH_DIR: string;
12
13
  export declare const DEFAULT_CODEX_TELEGRAM_HOME: string;
14
+ export declare const DEFAULT_ANTIGRAVITY_TELEGRAM_HOME: string;
13
15
  export declare const DEFAULT_AUTH_SYNC_STATE_PATH: string;
14
16
  export declare const DEFAULT_AUTH_SYNC_TEMP_DIR: string;
15
17
  export declare const DEFAULT_ENV_PATH: string;
@@ -17,6 +19,8 @@ export declare function resolveEnvPath(): string;
17
19
  export declare function getLoadedEnvPath(): string | null;
18
20
  export declare function loadEnv(): void;
19
21
  export interface AppConfig {
22
+ codexBotTokens: string[];
23
+ antigravityBotTokens: string[];
20
24
  tgBotToken: string;
21
25
  tgBotTokens: string[];
22
26
  tgMultiBotMode: boolean;
@@ -42,6 +46,13 @@ export interface AppConfig {
42
46
  opencodeServerPassword: string | null;
43
47
  opencodeServerStatePath: string;
44
48
  opencodeServerLogPath: string;
49
+ antigravityBotToken: string | null;
50
+ antigravityMultiBotMode: boolean;
51
+ antigravityDefaultRuntimeBotToken: string | null;
52
+ antigravityHome: string | null;
53
+ antigravityCliBin: string;
54
+ antigravityAuthDir: string;
55
+ antigravityDefaultModel: string;
45
56
  storePath: string;
46
57
  logLevel: LogLevel;
47
58
  defaultCwd: string;
@@ -100,7 +111,10 @@ export interface CodexApiProviderConfig {
100
111
  }
101
112
  export declare function loadConfig(): AppConfig;
102
113
  export declare function buildCodexApiProviderOverrides(providers: readonly CodexApiProviderConfig[], defaultProviderId?: string | null): string[];
114
+ export declare function validateCodexBotTokens(tokens: readonly string[]): void;
103
115
  export declare function validateOpencodeBotToken(token: string | null, codexTokens: readonly string[]): void;
116
+ export declare function validateAntigravityBotTokens(tokens: readonly string[], codexTokens: readonly string[], opencodeToken: string | null): void;
117
+ export declare function validateAntigravityBotToken(token: string | null, codexTokens: readonly string[], opencodeToken: string | null): void;
104
118
  export declare function parseCodexApiProviders(raw: string | undefined): CodexApiProviderConfig[];
105
119
  export declare function selectDefaultRuntimeBotToken(configuredTokens: string[], legacyToken: string | null): string | null;
106
120
  export declare function ensureAppDirs(config: AppConfig): void;
package/dist/config.js CHANGED
@@ -12,7 +12,9 @@ export const DEFAULT_CODEX_APP_SERVER_STATE_PATH = path.join(APP_HOME, 'runtime'
12
12
  export const DEFAULT_CODEX_APP_SERVER_LOG_PATH = path.join(APP_HOME, 'logs', 'codex-app-server.log');
13
13
  export const DEFAULT_OPENCODE_SERVER_STATE_PATH = path.join(APP_HOME, 'runtime', 'opencode-server.json');
14
14
  export const DEFAULT_OPENCODE_SERVER_LOG_PATH = path.join(APP_HOME, 'logs', 'opencode-server.log');
15
+ export const DEFAULT_ANTIGRAVITY_AUTH_DIR = path.join(os.homedir(), '.gemini', 'antigravity-cli');
15
16
  export const DEFAULT_CODEX_TELEGRAM_HOME = path.join(APP_HOME, 'codex', 'telegram');
17
+ export const DEFAULT_ANTIGRAVITY_TELEGRAM_HOME = path.join(APP_HOME, 'antigravity', 'telegram');
16
18
  export const DEFAULT_AUTH_SYNC_STATE_PATH = path.join(APP_HOME, 'runtime', 'auth-sync.json');
17
19
  export const DEFAULT_AUTH_SYNC_TEMP_DIR = path.join(APP_HOME, 'runtime', 'auth-sync');
18
20
  export const DEFAULT_ENV_PATH = path.join(APP_HOME, '.env');
@@ -42,26 +44,35 @@ export function loadEnv() {
42
44
  }
43
45
  export function loadConfig() {
44
46
  loadEnv();
45
- const configuredTokens = parseCommaSeparatedIds(process.env.TG_BOT_TOKENS);
46
- const legacyToken = optional('TG_BOT_TOKEN');
47
- const tgDefaultRuntimeBotToken = selectDefaultRuntimeBotToken(configuredTokens, legacyToken);
48
- const tgBotTokens = configuredTokens.length > 0
49
- ? configuredTokens
50
- : legacyToken
51
- ? [legacyToken]
52
- : [];
53
- if (tgBotTokens.length === 0) {
54
- throw new Error('TG_BOT_TOKENS or TG_BOT_TOKEN is required');
47
+ if (process.env.TG_BOT_TOKEN?.trim() || process.env.TG_BOT_TOKENS?.trim()) {
48
+ throw new Error('Configuration error: TG_BOT_TOKEN and TG_BOT_TOKENS have been removed. Please update .env to use CODEX_BOT_TOKENS and/or ANTIGRAVITY_BOT_TOKENS.');
55
49
  }
50
+ const codexBotTokens = parseCommaSeparatedIds(process.env.CODEX_BOT_TOKENS || process.env.CODEX_BOT_TOKEN);
51
+ const antigravityBotTokens = parseCommaSeparatedIds(process.env.ANTIGRAVITY_BOT_TOKENS || process.env.ANTIGRAVITY_BOT_TOKEN);
56
52
  const opencodeBotToken = optional('OPENCODE_BOT_TOKEN');
57
- validateOpencodeBotToken(opencodeBotToken, tgBotTokens);
53
+ if (codexBotTokens.length === 0 && antigravityBotTokens.length === 0) {
54
+ throw new Error('At least one Telegram bot token must be configured via CODEX_BOT_TOKENS or ANTIGRAVITY_BOT_TOKENS');
55
+ }
56
+ validateCodexBotTokens(codexBotTokens);
57
+ validateAntigravityBotTokens(antigravityBotTokens, codexBotTokens, opencodeBotToken);
58
+ validateOpencodeBotToken(opencodeBotToken, [...codexBotTokens, ...antigravityBotTokens]);
59
+ const codexDefaultRuntimeBotToken = selectDefaultRuntimeBotToken(codexBotTokens, optional('CODEX_DEFAULT_RUNTIME_BOT_TOKEN')) || codexBotTokens[0] || null;
60
+ const antigravityDefaultRuntimeBotToken = selectDefaultRuntimeBotToken(antigravityBotTokens, optional('ANTIGRAVITY_DEFAULT_RUNTIME_BOT_TOKEN')) ||
61
+ antigravityBotTokens[0] ||
62
+ null;
63
+ const totalBots = codexBotTokens.length + antigravityBotTokens.length;
64
+ const tgBotTokens = codexBotTokens.length > 0 ? codexBotTokens : antigravityBotTokens;
65
+ const tgBotToken = (codexBotTokens[0] || antigravityBotTokens[0]);
66
+ const antigravityBotToken = antigravityBotTokens[0] || null;
58
67
  const config = {
59
- tgBotToken: tgBotTokens[0],
68
+ codexBotTokens,
69
+ antigravityBotTokens,
70
+ tgBotToken,
60
71
  tgBotTokens,
61
- tgMultiBotMode: configuredTokens.length > 0,
62
- tgDefaultRuntimeBotToken,
72
+ tgMultiBotMode: totalBots > 1,
73
+ tgDefaultRuntimeBotToken: codexDefaultRuntimeBotToken,
63
74
  tgScopeBotId: null,
64
- tgRequireExplicitGroupAddressing: configuredTokens.length > 1,
75
+ tgRequireExplicitGroupAddressing: totalBots > 1,
65
76
  tgAllowedUserId: required('TG_ALLOWED_USER_ID'),
66
77
  tgAllowedChatId: optional('TG_ALLOWED_CHAT_ID'),
67
78
  tgAllowedTopicId: nullableIntEnv('TG_ALLOWED_TOPIC_ID'),
@@ -81,6 +92,13 @@ export function loadConfig() {
81
92
  opencodeServerPassword: optional('OPENCODE_SERVER_PASSWORD'),
82
93
  opencodeServerStatePath: process.env.OPENCODE_SERVER_STATE_PATH || DEFAULT_OPENCODE_SERVER_STATE_PATH,
83
94
  opencodeServerLogPath: process.env.OPENCODE_SERVER_LOG_PATH || DEFAULT_OPENCODE_SERVER_LOG_PATH,
95
+ antigravityBotToken,
96
+ antigravityMultiBotMode: antigravityBotTokens.length > 0,
97
+ antigravityDefaultRuntimeBotToken,
98
+ antigravityHome: process.env.ANTIGRAVITY_HOME?.trim() || null,
99
+ antigravityCliBin: process.env.ANTIGRAVITY_CLI_BIN || resolveCommand('agy') || '/home/wuya/.local/bin/agy',
100
+ antigravityAuthDir: process.env.ANTIGRAVITY_AUTH_DIR?.trim() || DEFAULT_ANTIGRAVITY_AUTH_DIR,
101
+ antigravityDefaultModel: process.env.ANTIGRAVITY_DEFAULT_MODEL?.trim() || 'gemini-3.8-flash-high',
84
102
  storePath: process.env.STORE_PATH || DEFAULT_STORE_PATH,
85
103
  logLevel: parseLogLevel(process.env.LOG_LEVEL || 'info'),
86
104
  defaultCwd: process.env.DEFAULT_CWD || process.cwd(),
@@ -149,13 +167,44 @@ export function buildCodexApiProviderOverrides(providers, defaultProviderId = nu
149
167
  }
150
168
  return overrides;
151
169
  }
170
+ export function validateCodexBotTokens(tokens) {
171
+ const seen = new Set();
172
+ for (const token of tokens) {
173
+ if (seen.has(token)) {
174
+ throw new Error(`CODEX_BOT_TOKENS contains duplicate Telegram bot token: ${token}`);
175
+ }
176
+ seen.add(token);
177
+ }
178
+ }
152
179
  export function validateOpencodeBotToken(token, codexTokens) {
153
180
  if (!token)
154
181
  return;
155
182
  if (codexTokens.includes(token)) {
156
- throw new Error('OPENCODE_BOT_TOKEN must use a different bot from TG_BOT_TOKENS/TG_BOT_TOKEN');
183
+ throw new Error('OPENCODE_BOT_TOKEN must use a different bot from CODEX_BOT_TOKENS');
157
184
  }
158
185
  }
186
+ export function validateAntigravityBotTokens(tokens, codexTokens, opencodeToken) {
187
+ if (tokens.length === 0)
188
+ return;
189
+ const seen = new Set();
190
+ for (const token of tokens) {
191
+ if (seen.has(token)) {
192
+ throw new Error(`ANTIGRAVITY_BOT_TOKENS contains duplicate Telegram bot token: ${token}`);
193
+ }
194
+ seen.add(token);
195
+ if (codexTokens.includes(token)) {
196
+ throw new Error('ANTIGRAVITY_BOT_TOKENS must use a different bot from CODEX_BOT_TOKENS');
197
+ }
198
+ if (opencodeToken && token === opencodeToken) {
199
+ throw new Error('ANTIGRAVITY_BOT_TOKENS must use a different bot from OPENCODE_BOT_TOKEN');
200
+ }
201
+ }
202
+ }
203
+ export function validateAntigravityBotToken(token, codexTokens, opencodeToken) {
204
+ if (!token)
205
+ return;
206
+ validateAntigravityBotTokens([token], codexTokens, opencodeToken);
207
+ }
159
208
  export function parseCodexApiProviders(raw) {
160
209
  if (!raw?.trim())
161
210
  return [];
@@ -2,8 +2,8 @@ import type { AppConfig } from '../config.js';
2
2
  import type { Logger } from '../logger.js';
3
3
  import type { BridgeStore } from '../store/database.js';
4
4
  import { type AuthSyncClusterAuditResult } from '../auth/cross_node_sync.js';
5
- import type { RuntimeStatus } from '../types.js';
6
- import type { TelegramGateway, TelegramTextEvent } from '../telegram/gateway.js';
5
+ import type { AppLocale, RuntimeStatus } from '../types.js';
6
+ import type { TelegramGateway, TelegramTextEvent, TelegramCallbackEvent } from '../telegram/gateway.js';
7
7
  import { BridgeMessagingRouter } from '../channels/bridge_messaging_router.js';
8
8
  import type { CodexAppClient } from '../codex_app/client.js';
9
9
  import type { SelfUpdateRuntime, SelfUpdateStatus } from '../update.js';
@@ -71,6 +71,7 @@ export declare class BridgeSessionCore {
71
71
  private pendingForceTakeovers;
72
72
  private pendingLoginScopesById;
73
73
  private pendingAuthAddsByLoginId;
74
+ private authLoginRecoveryTimers;
74
75
  private latestTurnDiffs;
75
76
  private threadTokenUsageAlerts;
76
77
  private pendingAuthChoiceLists;
@@ -119,6 +120,7 @@ export declare class BridgeSessionCore {
119
120
  * (used by the Weixin adapter).
120
121
  */
121
122
  dispatchInboundLikeTelegramText(event: TelegramTextEvent): void;
123
+ hasPendingInteraction(scopeId: string): boolean;
122
124
  /** Start Codex app-server transport and attach RPC listeners. */
123
125
  startCodexApp(): Promise<void>;
124
126
  /** Begin Telegram Bot API long-polling after handlers and Codex are ready. */
@@ -135,7 +137,7 @@ export declare class BridgeSessionCore {
135
137
  private handleWatchCommand;
136
138
  private handleThreadArchiveIndexCommand;
137
139
  private handleThreadRenameIndexCommand;
138
- private handleCallback;
140
+ handleCallback(event: TelegramCallbackEvent): Promise<void>;
139
141
  private handleApprovalTextCommand;
140
142
  private handleNotification;
141
143
  private handleServerRequest;
@@ -155,6 +157,9 @@ export declare class BridgeSessionCore {
155
157
  private handleServerRequestResolved;
156
158
  private handleAccountLoginCompleted;
157
159
  private restorePendingAuthAdd;
160
+ private scheduleAuthLoginRecovery;
161
+ private recoverAuthLogin;
162
+ private reconcileAuthLoginCredential;
158
163
  private restoreAuthAfterAddFailure;
159
164
  private handleAccountUpdated;
160
165
  private handleRateLimitsUpdated;
@@ -327,7 +332,7 @@ export declare class BridgeSessionCore {
327
332
  private clearProactiveAuthRefreshTimer;
328
333
  private runProactiveAuthRefresh;
329
334
  private recordProactiveAuthRefreshStatus;
330
- private handleAuthCommand;
335
+ handleAuthCommand(scopeId: string, locale: AppLocale, args: string[]): Promise<void>;
331
336
  private handleAuthSyncCommand;
332
337
  private runAuthSafeSyncAll;
333
338
  private runAuthClusterAudit;
@@ -444,7 +449,9 @@ export declare class BridgeSessionCore {
444
449
  private handleNavigationCallback;
445
450
  private showWherePanel;
446
451
  private handleThreadListNavigationCallback;
447
- private showThreadsPanel;
452
+ showThreadsPanel(scopeId: string, messageId?: number, searchTerm?: string | null, locale?: AppLocale, options?: {
453
+ offset?: number;
454
+ }, archived?: boolean): Promise<void>;
448
455
  private showModelSettingsPanel;
449
456
  private showSetupPanel;
450
457
  private showAccessSettingsPanel;