@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,394 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import path from 'node:path';
3
+ import { normalizeAgyModelId } from './client.js';
4
+ import { isCapacityOrUnavailableError, isVerificationError, isQuotaOrAuthError, isTransientNetworkError, } from './events.js';
5
+ import { buildAttachmentPrompt } from '../telegram/media.js';
6
+ import { AntigravitySubagentTracker } from './subagents.js';
7
+ function extractToolSummary(name, parameters) {
8
+ if (!parameters)
9
+ return undefined;
10
+ if (typeof parameters.CommandLine === 'string') {
11
+ return `$ ${parameters.CommandLine.slice(0, 70)}`;
12
+ }
13
+ if (typeof parameters.TargetFile === 'string') {
14
+ return path.basename(parameters.TargetFile);
15
+ }
16
+ if (typeof parameters.AbsolutePath === 'string') {
17
+ return path.basename(parameters.AbsolutePath);
18
+ }
19
+ if (typeof parameters.path === 'string') {
20
+ return path.basename(parameters.path);
21
+ }
22
+ if (typeof parameters.query === 'string') {
23
+ return parameters.query.slice(0, 60);
24
+ }
25
+ if (typeof parameters.Url === 'string') {
26
+ return parameters.Url.slice(0, 60);
27
+ }
28
+ if (typeof parameters.toolSummary === 'string') {
29
+ return parameters.toolSummary.slice(0, 60);
30
+ }
31
+ if (typeof parameters.toolAction === 'string') {
32
+ return parameters.toolAction.slice(0, 60);
33
+ }
34
+ return undefined;
35
+ }
36
+ export class AntigravityEngineAdapter {
37
+ id = 'antigravity';
38
+ name = 'Google Antigravity (AGY)';
39
+ client;
40
+ defaultModel;
41
+ auth;
42
+ logger;
43
+ baseDir;
44
+ constructor(client, defaultModel = 'gemini-3.8-flash', auth, logger, baseDir) {
45
+ this.client = client;
46
+ this.defaultModel = defaultModel;
47
+ this.auth = auth;
48
+ this.logger = logger;
49
+ this.baseDir = baseDir ?? auth?.authDir;
50
+ }
51
+ async listModels() {
52
+ const rawModels = await this.client.listModels();
53
+ const seen = new Set();
54
+ const result = [];
55
+ for (const raw of rawModels) {
56
+ const m = normalizeAgyModelId(raw);
57
+ if (seen.has(m))
58
+ continue;
59
+ seen.add(m);
60
+ let displayName = m;
61
+ if (m === 'gemini-3.8-flash')
62
+ displayName = 'Gemini 3.8 Flash';
63
+ else if (m === 'gemini-3.7-flash')
64
+ displayName = 'Gemini 3.7 Flash';
65
+ else if (m === 'gemini-3.6-flash')
66
+ displayName = 'Gemini 3.6 Flash';
67
+ else if (m === 'gemini-3.1-pro')
68
+ displayName = 'Gemini 3.1 Pro';
69
+ else if (m === 'claude-sonnet-4-6')
70
+ displayName = 'Claude Sonnet 4.6';
71
+ else if (m === 'claude-opus-4-6')
72
+ displayName = 'Claude Opus 4.6';
73
+ else if (m === 'gpt-oss-120b')
74
+ displayName = 'GPT-OSS 120B';
75
+ else
76
+ displayName = m.replace(/^gemini-/, 'Gemini ').replace(/^claude-/, 'Claude ');
77
+ result.push({
78
+ id: m,
79
+ name: displayName,
80
+ description: m.includes('flash')
81
+ ? 'Fast, high-throughput model'
82
+ : m.includes('pro')
83
+ ? 'Deep reasoning & complex tasks'
84
+ : undefined,
85
+ isDefault: m === this.defaultModel || (this.defaultModel.startsWith(m) && !m.includes('-')),
86
+ });
87
+ }
88
+ return result;
89
+ }
90
+ executeTurn(request) {
91
+ const emitter = new EventEmitter();
92
+ let prompt = request.prompt;
93
+ if (request.stagedAttachments && request.stagedAttachments.length > 0 && !request.prompt.includes('Telegram attachments:')) {
94
+ prompt = buildAttachmentPrompt(request.prompt, request.stagedAttachments);
95
+ }
96
+ const targetModel = (!request.model || request.model === 'default') ? this.defaultModel : request.model;
97
+ const agTurn = this.client.executeTurn({
98
+ prompt,
99
+ conversationId: request.threadId,
100
+ cwd: request.cwd,
101
+ model: targetModel,
102
+ effort: request.effort ?? null,
103
+ });
104
+ let subagentTracker = null;
105
+ let pollTimer = null;
106
+ let turnFinished = false;
107
+ const ensureTracker = (convId) => {
108
+ if (!convId || subagentTracker)
109
+ return;
110
+ subagentTracker = new AntigravitySubagentTracker(convId, this.baseDir, this.logger);
111
+ };
112
+ if (request.threadId) {
113
+ ensureTracker(request.threadId);
114
+ }
115
+ const stopSubagentPolling = () => {
116
+ turnFinished = true;
117
+ if (pollTimer) {
118
+ clearInterval(pollTimer);
119
+ pollTimer = null;
120
+ }
121
+ };
122
+ const startSubagentPolling = () => {
123
+ if (pollTimer || turnFinished)
124
+ return;
125
+ pollTimer = setInterval(async () => {
126
+ if (turnFinished) {
127
+ stopSubagentPolling();
128
+ return;
129
+ }
130
+ if (!subagentTracker && agTurn.conversationId) {
131
+ ensureTracker(agTurn.conversationId);
132
+ }
133
+ if (subagentTracker) {
134
+ try {
135
+ const res = await subagentTracker.poll();
136
+ if (res.newToolEvents && res.newToolEvents.length > 0) {
137
+ for (const ev of res.newToolEvents) {
138
+ emitter.emit('tool', {
139
+ name: `[${ev.subagentName}] ${ev.toolName}`,
140
+ args: ev.args,
141
+ status: ev.status,
142
+ stepIndex: ev.stepIndex,
143
+ summary: ev.summary,
144
+ });
145
+ }
146
+ }
147
+ }
148
+ catch {
149
+ // ignore polling errors
150
+ }
151
+ }
152
+ }, 1000);
153
+ pollTimer.unref?.();
154
+ };
155
+ startSubagentPolling();
156
+ let emittedConvId = null;
157
+ agTurn.on('event', (ev) => {
158
+ if (ev.conversationId) {
159
+ ensureTracker(ev.conversationId);
160
+ if (emittedConvId !== ev.conversationId) {
161
+ emittedConvId = ev.conversationId;
162
+ emitter.emit('conversation', ev.conversationId);
163
+ }
164
+ }
165
+ switch (ev.kind) {
166
+ case 'text':
167
+ emitter.emit('delta', ev.delta);
168
+ break;
169
+ case 'tool':
170
+ emitter.emit('tool', {
171
+ name: ev.toolName,
172
+ args: ev.parameters,
173
+ output: ev.output,
174
+ status: ev.state === 'ACTIVE' ? 'running' : ev.state === 'ERROR' ? 'failed' : 'completed',
175
+ stepIndex: ev.stepIndex,
176
+ summary: extractToolSummary(ev.toolName, ev.parameters),
177
+ });
178
+ break;
179
+ case 'result': {
180
+ stopSubagentPolling();
181
+ const res = {
182
+ kind: 'result',
183
+ status: ev.status === 'SUCCESS' ? 'SUCCESS' : 'ERROR',
184
+ response: ev.response || ev.error || (ev.status === 'ERROR' ? 'Antigravity execution failed' : ''),
185
+ error: ev.error || (ev.status === 'ERROR' ? 'Antigravity execution failed' : undefined),
186
+ conversationId: ev.conversationId,
187
+ usage: ev.usage
188
+ ? {
189
+ inputTokens: ev.usage.input_tokens,
190
+ outputTokens: ev.usage.output_tokens,
191
+ cachedTokens: ev.usage.cache_read_tokens,
192
+ totalTokens: ev.usage.total_tokens,
193
+ }
194
+ : undefined,
195
+ };
196
+ emitter.emit('result', res);
197
+ break;
198
+ }
199
+ }
200
+ });
201
+ agTurn.on('error', (err) => {
202
+ stopSubagentPolling();
203
+ emitter.emit('error', err);
204
+ });
205
+ agTurn.on('exit', (code) => {
206
+ stopSubagentPolling();
207
+ emitter.emit('exit', code);
208
+ });
209
+ return {
210
+ get turnId() {
211
+ return agTurn.conversationId ?? undefined;
212
+ },
213
+ cancel: () => {
214
+ stopSubagentPolling();
215
+ agTurn.cancel();
216
+ },
217
+ waitForResult: async () => {
218
+ try {
219
+ const ev = await agTurn.waitForResult();
220
+ if (!ev || ev.kind !== 'result')
221
+ return null;
222
+ return {
223
+ kind: 'result',
224
+ status: ev.status === 'SUCCESS' ? 'SUCCESS' : 'ERROR',
225
+ response: ev.response || ev.error || (ev.status === 'ERROR' ? 'Antigravity execution failed' : ''),
226
+ error: ev.error || (ev.status === 'ERROR' ? 'Antigravity execution failed' : undefined),
227
+ conversationId: ev.conversationId,
228
+ usage: ev.usage
229
+ ? {
230
+ inputTokens: ev.usage.input_tokens,
231
+ outputTokens: ev.usage.output_tokens,
232
+ cachedTokens: ev.usage.cache_read_tokens,
233
+ totalTokens: ev.usage.total_tokens,
234
+ }
235
+ : undefined,
236
+ };
237
+ }
238
+ finally {
239
+ stopSubagentPolling();
240
+ }
241
+ },
242
+ on: (event, listener) => {
243
+ emitter.on(event, listener);
244
+ },
245
+ };
246
+ }
247
+ async preflightTurn(_request) {
248
+ void _request;
249
+ if (this.auth) {
250
+ try {
251
+ await this.auth.ensureActiveTokenFresh(300);
252
+ }
253
+ catch (err) {
254
+ this.logger?.warn('antigravity.preflight_refresh_failed', { error: String(err) });
255
+ }
256
+ }
257
+ }
258
+ async handleTurnError(ctx) {
259
+ const isVerification = isVerificationError(ctx.error);
260
+ const isQuota = !isVerification && isQuotaOrAuthError(ctx.error);
261
+ if (isVerification && this.auth && ctx.retryCount < 3) {
262
+ try {
263
+ const active = await this.auth.getActiveAccount();
264
+ const failingName = active?.name || 'unknown';
265
+ const failingEmail = active?.email || failingName;
266
+ this.auth.pauseAccount(failingName);
267
+ this.auth.markCooldown(failingName, 60 * 60 * 1000);
268
+ if (active?.email) {
269
+ this.auth.pauseAccount(active.email);
270
+ this.auth.markCooldown(active.email, 60 * 60 * 1000);
271
+ }
272
+ const rotated = await this.auth.rotateNextCandidate(failingName);
273
+ const nextAccountName = rotated.account.email || rotated.account.name;
274
+ try {
275
+ await ctx.sendMessage(ctx.request.locale === 'zh'
276
+ ? `🛡️ **检测到 Google 安全验证风控 (Verification Required)**\n\n` +
277
+ `• **受限账号**: \`${failingEmail}\` (已自动暂停并冷却)\n` +
278
+ `• **故障恢复**: 已无感切换至备用账号 \`${nextAccountName}\`,正在继续执行您的任务…\n\n` +
279
+ `💡 **如何人工解除该账号风控**:\n` +
280
+ `1. 可在终端/CLI 执行 \`agy -p "hi"\`,在弹出的浏览器完成人机验证;\n` +
281
+ `2. 或在 Telegram 发送 \`/login\` 重新授权该账号后即可恢复。`
282
+ : `🛡️ **Google Security Verification Triggered (Verification Required)**\n\n` +
283
+ `• **Affected**: \`${failingEmail}\` (paused & cooling)\n` +
284
+ `• **Failover**: Switched to \`${nextAccountName}\`, continuing your task…\n\n` +
285
+ `💡 **How to resolve**: Complete verification via \`agy -p "hi"\` in browser or run \`/login\` in Telegram.`);
286
+ }
287
+ catch {
288
+ /* messaging may fail during network glitch */
289
+ }
290
+ await ctx.retryTurn(ctx.retryCount + 1);
291
+ return true;
292
+ }
293
+ catch (err) {
294
+ this.logger?.warn('antigravity.verification_rotation_failed', {
295
+ error: err instanceof Error ? err.message : String(err),
296
+ });
297
+ }
298
+ }
299
+ if (isQuota && this.auth && ctx.retryCount < 3) {
300
+ try {
301
+ const active = await this.auth.getActiveAccount();
302
+ if (active) {
303
+ this.auth.markCooldown(active.name, 30 * 60 * 1000);
304
+ if (active.email)
305
+ this.auth.markCooldown(active.email, 30 * 60 * 1000);
306
+ }
307
+ const rotated = await this.auth.rotateNextCandidate(active?.name);
308
+ try {
309
+ await ctx.sendMessage(ctx.request.locale === 'zh'
310
+ ? `⚠️ 检测到当前账号配额超限或凭据失效,已标记进入 30 分钟冷却,自动切换到账号 \`${rotated.account.email || rotated.account.name}\` 并重试…`
311
+ : `⚠️ Quota or auth issue detected. Marked account into 30m cooldown, switched to \`${rotated.account.email || rotated.account.name}\` and retrying…`);
312
+ }
313
+ catch {
314
+ /* messaging may fail during network glitch */
315
+ }
316
+ await ctx.retryTurn(ctx.retryCount + 1);
317
+ return true;
318
+ }
319
+ catch (rotateErr) {
320
+ this.logger?.warn('antigravity.rotation_failed', {
321
+ error: rotateErr instanceof Error ? rotateErr.message : String(rotateErr),
322
+ });
323
+ }
324
+ }
325
+ const isCapacity = isCapacityOrUnavailableError(ctx.error);
326
+ if (isCapacity && ctx.retryCount < 2) {
327
+ try {
328
+ await ctx.sendMessage(ctx.request.locale === 'zh'
329
+ ? `⏳ Google 模型服务临时繁忙 (503 Unavailable),等待 2 秒后自动重试 (第 ${ctx.retryCount + 1}/2 次)…`
330
+ : `⏳ Google model temporarily unavailable (503), retrying in 2s (attempt ${ctx.retryCount + 1}/2)…`);
331
+ }
332
+ catch {
333
+ /* messaging may fail during network glitch */
334
+ }
335
+ await new Promise((r) => setTimeout(r, 2000));
336
+ await ctx.retryTurn(ctx.retryCount + 1);
337
+ return true;
338
+ }
339
+ if (isCapacity) {
340
+ try {
341
+ await ctx.sendMessage(ctx.request.locale === 'zh'
342
+ ? `⚠️ **Google 模型容量繁忙 (503 UNAVAILABLE)**\n\nGoogle 服务端当前模型 (\`${ctx.request.model}\`) 暂时无可用容量。\n\n💡 **建议方案**:\n• 稍等 1 分钟后重试\n• 发送 /setup 或 /models 切换为 \`gemini-3.1-pro-high\` 继续`
343
+ : `⚠️ **Model Capacity Unavailable (503)**\n\nGoogle servers have no capacity for \`${ctx.request.model}\` right now.\n\n💡 Tip: Wait 1 minute or switch model via /setup or /models.`);
344
+ }
345
+ catch {
346
+ /* messaging may fail during network glitch */
347
+ }
348
+ return true;
349
+ }
350
+ const isNetwork = isTransientNetworkError(ctx.error);
351
+ if (isNetwork && ctx.retryCount < 3) {
352
+ const backoffMs = Math.min(15000, 3000 * Math.pow(2, ctx.retryCount));
353
+ const delaySec = Math.round(backoffMs / 1000);
354
+ try {
355
+ await ctx.sendMessage(ctx.request.locale === 'zh'
356
+ ? `🌐 **检测到网络波动或连接中断**\n\n` +
357
+ `• **错误提示**: \`${ctx.error.slice(0, 100)}\`\n` +
358
+ `• **安全重试**: 将在 ${delaySec} 秒后自动重试并在网络恢复后继续执行 (第 ${ctx.retryCount + 1}/3 次)…`
359
+ : `🌐 **Network Fluctuation Detected**\n\n` +
360
+ `• **Error**: \`${ctx.error.slice(0, 100)}\`\n` +
361
+ `• **Auto-Retry**: Retrying in ${delaySec}s to resume execution after network recovery (attempt ${ctx.retryCount + 1}/3)…`);
362
+ }
363
+ catch {
364
+ /* messaging may fail during network glitch */
365
+ }
366
+ await new Promise((r) => setTimeout(r, backoffMs));
367
+ try {
368
+ await ctx.retryTurn(ctx.retryCount + 1);
369
+ }
370
+ catch (err) {
371
+ this.logger?.error('antigravity.retry_turn_failed', { error: String(err) });
372
+ }
373
+ return true;
374
+ }
375
+ if (isNetwork) {
376
+ try {
377
+ await ctx.sendMessage(ctx.request.locale === 'zh'
378
+ ? `⚠️ **网络连接连续中断 (已重试 3 次)**\n\n` +
379
+ `• **错误详情**: \`${ctx.error.slice(0, 150)}\`\n` +
380
+ `• **说明**: 代理或网络暂时无法稳定连接 Google 服务端。\n\n` +
381
+ `💡 **建议方案**:\n• 请检查本机网络与代理设置(如 Clash / v2ray / proxy)\n• 网络恢复后直接再次发送您的指令即可继续`
382
+ : `⚠️ **Network Connection Failed (Retried 3 times)**\n\n` +
383
+ `• **Error**: \`${ctx.error.slice(0, 150)}\`\n` +
384
+ `• **Note**: Unable to reach Google servers stably.\n\n` +
385
+ `💡 Tip: Please check your network/proxy connection, and send your message again once restored.`);
386
+ }
387
+ catch {
388
+ /* messaging may fail during network glitch */
389
+ }
390
+ return true;
391
+ }
392
+ return false;
393
+ }
394
+ }
@@ -0,0 +1,126 @@
1
+ import type { Logger } from '../logger.js';
2
+ export declare const GOOGLE_OAUTH_CLIENT_ID: string;
3
+ export declare const GOOGLE_OAUTH_CLIENT_SECRET: string;
4
+ export declare const GOOGLE_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token";
5
+ export declare const GOOGLE_QUOTA_ENDPOINT = "https://daily-cloudcode-pa.googleapis.com/v1internal:retrieveUserQuotaSummary";
6
+ export interface AntigravityQuotaSnapshot {
7
+ fiveHourPercent: number | null;
8
+ weeklyPercent: number | null;
9
+ resetTime5h: string | null;
10
+ resetTimeWeekly: string | null;
11
+ thirdParty5hPercent?: number | null;
12
+ thirdPartyWeeklyPercent?: number | null;
13
+ updatedAt: number;
14
+ }
15
+ export interface AntigravityAccount {
16
+ name: string;
17
+ filePath: string;
18
+ email: string | null;
19
+ isActive: boolean;
20
+ hasRefreshToken: boolean;
21
+ expiry: number | null;
22
+ isCooldown?: boolean;
23
+ cooldownRemainingSec?: number;
24
+ isPaused?: boolean;
25
+ quota?: AntigravityQuotaSnapshot | null;
26
+ }
27
+ export interface RefreshResult {
28
+ success: boolean;
29
+ account?: AntigravityAccount | undefined;
30
+ error?: string | undefined;
31
+ newExpiry?: number | undefined;
32
+ }
33
+ export interface RefreshAllSummary {
34
+ total: number;
35
+ refreshed: number;
36
+ failed: number;
37
+ errors: Record<string, string>;
38
+ }
39
+ export interface ImportAccountResult {
40
+ success: boolean;
41
+ account?: AntigravityAccount | undefined;
42
+ error?: string | undefined;
43
+ createdFileName?: string | undefined;
44
+ }
45
+ export interface PendingLoginSession {
46
+ scopeId: string;
47
+ codeVerifier: string;
48
+ state: string;
49
+ createdAt: number;
50
+ }
51
+ export declare function parseExpiryToMs(val: unknown): number | null;
52
+ export declare function parseEmailFromIdToken(idToken: string | undefined | null): string | null;
53
+ export declare function formatQuotaResetTime(isoString: string | null | undefined): string;
54
+ export declare class AntigravityAuthManager {
55
+ readonly authDir: string;
56
+ readonly activeTokenPath: string;
57
+ readonly pausedAccountsPath: string;
58
+ private readonly logger;
59
+ private readonly fetchFn;
60
+ private readonly cooldowns;
61
+ private readonly pendingLogins;
62
+ private readonly quotaCache;
63
+ private readonly pausedAccounts;
64
+ private keepAliveTimer;
65
+ constructor(authDir?: string, logger?: Logger, fetchFn?: typeof fetch);
66
+ startBrowserLogin(scopeId: string): {
67
+ authUrl: string;
68
+ state: string;
69
+ };
70
+ hasPendingLogin(scopeId: string): boolean;
71
+ cancelBrowserLogin(scopeId: string): boolean;
72
+ completeBrowserLogin(scopeId: string, rawInput: string): Promise<ImportAccountResult>;
73
+ markCooldown(nameOrEmail: string, durationMs?: number): void;
74
+ clearCooldown(nameOrEmail: string): void;
75
+ isCooldown(nameOrEmail: string): boolean;
76
+ getCooldownRemainingSec(nameOrEmail: string): number;
77
+ private loadPausedAccounts;
78
+ private savePausedAccounts;
79
+ isAccountPaused(nameOrEmail: string): boolean;
80
+ togglePauseAccount(targetNameOrEmail: string): boolean;
81
+ pauseAccount(targetNameOrEmail: string): void;
82
+ resumeAccount(targetNameOrEmail: string): void;
83
+ diagnoseAndRepairAccount(targetNameOrEmail: string): Promise<{
84
+ ok: boolean;
85
+ accountName: string;
86
+ email: string | null;
87
+ message: string;
88
+ refreshed: boolean;
89
+ }>;
90
+ diagnoseAndRepairAll(): Promise<{
91
+ total: number;
92
+ healthy: number;
93
+ repaired: number;
94
+ failed: number;
95
+ summary: string[];
96
+ }>;
97
+ getCachedQuota(name: string, email?: string | null): AntigravityQuotaSnapshot | null;
98
+ fetchQuotaForAccount(targetNameOrEmail: string, force?: boolean): Promise<AntigravityQuotaSnapshot | null>;
99
+ populateQuotas(candidates: AntigravityAccount[], force?: boolean): Promise<void>;
100
+ listCandidates(includeQuotas?: boolean, forceQuotaRefresh?: boolean): Promise<AntigravityAccount[]>;
101
+ getActiveAccount(): Promise<AntigravityAccount | null>;
102
+ switchAccount(targetNameOrEmail: string): Promise<{
103
+ success: boolean;
104
+ account: AntigravityAccount;
105
+ }>;
106
+ rotateNextCandidate(currentAccountName?: string): Promise<{
107
+ success: boolean;
108
+ account: AntigravityAccount;
109
+ }>;
110
+ refreshTokenForAccount(targetNameOrEmail: string): Promise<RefreshResult>;
111
+ refreshAllTokens(): Promise<RefreshAllSummary>;
112
+ ensureActiveTokenFresh(thresholdSec?: number): Promise<boolean>;
113
+ importAccountFromJson(rawJson: string, preferredName?: string): Promise<ImportAccountResult>;
114
+ startKeepAlive(intervalMs?: number): void;
115
+ stopKeepAlive(): void;
116
+ runKeepAliveCheck(): Promise<void>;
117
+ private parseAccountFile;
118
+ private readActiveTokenContent;
119
+ private atomicCopy;
120
+ private atomicWriteJson;
121
+ }
122
+ /**
123
+ * Links Antigravity OAuth tokens and paused account state from sourceAuthDir into targetAuthDir.
124
+ * Does NOT link conversation_summaries.db or brain/ to keep conversation history isolated per bot.
125
+ */
126
+ export declare function linkAntigravityAuthTokens(sourceAuthDir: string, targetAuthDir: string): void;