@foxden-app/foxclaw 0.10.0 → 0.11.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/.env.example +9 -8
- package/CHANGELOG.md +43 -0
- package/dist/antigravity/adapter.d.ts +2 -1
- package/dist/antigravity/adapter.js +180 -42
- package/dist/antigravity/auth.d.ts +5 -0
- package/dist/antigravity/auth.js +37 -0
- package/dist/antigravity/client.js +8 -2
- package/dist/antigravity/controller.d.ts +10 -0
- package/dist/antigravity/controller.js +189 -28
- package/dist/antigravity/events.d.ts +1 -0
- package/dist/antigravity/events.js +29 -0
- package/dist/antigravity/runtime.d.ts +2 -0
- package/dist/antigravity/subagents.d.ts +66 -0
- package/dist/antigravity/subagents.js +459 -0
- package/dist/config.d.ts +3 -1
- package/dist/config.js +36 -29
- package/dist/controller/controller.d.ts +4 -0
- package/dist/controller/controller.js +71 -0
- package/dist/core/engine_spi.d.ts +1 -0
- package/dist/core/orchestrator.d.ts +6 -1
- package/dist/core/orchestrator.js +291 -139
- package/dist/main.js +346 -551
- package/dist/opencode/adapter.js +1 -0
- package/dist/opencode/controller.js +2 -2
- package/dist/store/database.d.ts +3 -0
- package/dist/store/database.js +33 -0
- package/dist/telegram/api.d.ts +1 -0
- package/dist/telegram/api.js +60 -12
- package/dist/telegram/bot_home.js +5 -4
- package/dist/types.d.ts +2 -0
- package/dist/update.js +17 -1
- package/package.json +1 -1
package/.env.example
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
# Required
|
|
2
|
-
# Comma-separated bot tokens.
|
|
3
|
-
#
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
1
|
+
# Required: one or more Telegram bot tokens for Codex execution.
|
|
2
|
+
# Comma-separated bot tokens. The 1st bot shares the default terminal CODEX_HOME (~/.codex).
|
|
3
|
+
# Additional bots receive independent isolated homes (~/.foxclaw/codex/telegram/@TelegramUsername/home).
|
|
4
|
+
CODEX_BOT_TOKENS=<codex_telegram_bot_token>
|
|
5
|
+
|
|
6
|
+
# Optional: one or more Telegram bot tokens for Google Antigravity execution.
|
|
7
|
+
# Comma-separated bot tokens. The 1st bot shares the default Antigravity home (~/.gemini/antigravity-cli).
|
|
8
|
+
# Additional bots receive independent isolated homes (~/.foxclaw/antigravity/telegram/@TelegramUsername/home).
|
|
9
|
+
# ANTIGRAVITY_BOT_TOKENS=<antigravity_telegram_bot_token>
|
|
9
10
|
|
|
10
11
|
# Required: only this Telegram user can control the bridge
|
|
11
12
|
TG_ALLOWED_USER_ID=<telegram_user_id>
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,49 @@
|
|
|
2
2
|
|
|
3
3
|
All notable FoxClaw changes are listed here. Each release note is bilingual so GitHub Releases and the npm package are useful to both Chinese and English readers.
|
|
4
4
|
|
|
5
|
+
## 0.11.1 - 2026-09-30
|
|
6
|
+
|
|
7
|
+
### 中文
|
|
8
|
+
- **Antigravity 控制器自升级完成通知闭环**:
|
|
9
|
+
- 修复通过 Antigravity 机器人(瓦小安)触发 `/update` 后,服务自升级成功重启却未能轮询并投递升级完成卡片及更新日志的问题。
|
|
10
|
+
- 补齐 `UnifiedBridgeCore` 的自升级状态轮询(`pollSelfUpdateStatus`)与多 Bot 归属检测。
|
|
11
|
+
- **只读观察器(Watcher)会话切换自动解绑**:
|
|
12
|
+
- 修复当前聊天会话切换至新任务后,后台旧任务 Watcher 持续抢占消息卡片导致跨任务串屏与日志污染的问题。
|
|
13
|
+
- `pollWatcher` 检测到当前绑定会话与被观察会话不一致时,自动静默卸载 Watcher。
|
|
14
|
+
|
|
15
|
+
### English
|
|
16
|
+
- **Antigravity Post-Update Notification Delivery**:
|
|
17
|
+
- Fixed an issue where running `/update` on Antigravity bots (e.g. `@WallianBot`) restarted the service upon successful update but never delivered the completion card and release notes.
|
|
18
|
+
- Implemented `pollSelfUpdateStatus` in `UnifiedBridgeCore` with scope ownership validation.
|
|
19
|
+
- **Watcher Auto-Detachment on Thread Switch**:
|
|
20
|
+
- Fixed an issue where background watchers from other conversations continued hijacking Telegram previews after the chat scope switched to a different task.
|
|
21
|
+
- `pollWatcher` now silently detaches when the active thread binding no longer matches the observed conversation.
|
|
22
|
+
|
|
23
|
+
## 0.11.0 - 2026-09-30
|
|
24
|
+
|
|
25
|
+
### 中文
|
|
26
|
+
- **多后端 Telegram Bot 变量重构与后端原生隔离**:
|
|
27
|
+
- 弃用原有的 `TG_BOT_TOKEN`/`TG_BOT_TOKENS`,引入面向具体执行后端的 `CODEX_BOT_TOKENS` 与 `ANTIGRAVITY_BOT_TOKENS`。
|
|
28
|
+
- 每类后端的首个 Bot 默认与本地终端 CLI 共享主目录(Codex 共享 `~/.codex`,Antigravity 共享 `~/.gemini/antigravity-cli`),后续实例自动创建隔离主目录。
|
|
29
|
+
- 隔离目录路径统一升级为人类可读格式(如 `@WallianBot`、`@waxiaosheBot`),彻底告别原有的裸数字 ID 目录。
|
|
30
|
+
- **作用域会话继承与迁移容错增强**:
|
|
31
|
+
- 修复多 Bot 模式下旧未命名空间会话因历史脏数据导致无法自动迁移的问题;加入 `updated_at` 时间戳比对,自动无缝承接最新正在执行的任务线程。
|
|
32
|
+
- 增强 `scope_backend_bindings` 的自动迁移与跨引擎会话保持能力。
|
|
33
|
+
- **网络鲁棒性与服务重启自愈**:
|
|
34
|
+
- 强化 Telegram 长轮询与分片投递在网络断连、代理抖动时的自动恢复机制。
|
|
35
|
+
- 修复服务重启后活跃任务的断点自动续跑逻辑。
|
|
36
|
+
|
|
37
|
+
### English
|
|
38
|
+
- **Backend-Centric Bot Variables & Native Home Isolation**:
|
|
39
|
+
- Deprecated legacy `TG_BOT_TOKEN`/`TG_BOT_TOKENS` in favor of backend-specific `CODEX_BOT_TOKENS` and `ANTIGRAVITY_BOT_TOKENS`.
|
|
40
|
+
- The first bot of each backend type shares the default terminal CLI home (`~/.codex` for Codex, `~/.gemini/antigravity-cli` for Antigravity), while additional bots receive fully isolated homes.
|
|
41
|
+
- Isolated directories are now standardized to human-readable Telegram usernames (e.g. `@WallianBot`, `@waxiaosheBot`).
|
|
42
|
+
- **Resilient Scope Migration & Session Inheritance**:
|
|
43
|
+
- Resolved scope migration conflicts where stale historical records blocked recent unnamespaced turns from being adopted under multi-bot mode.
|
|
44
|
+
- Added timestamp-based migration for `chat_bindings` and `scope_backend_bindings` to ensure seamless continuation of active sessions across restarts.
|
|
45
|
+
- **Network Resilience & Service Auto-Recovery**:
|
|
46
|
+
- Enhanced polling and chunk delivery retry mechanisms during transient network resets and proxy timeouts.
|
|
47
|
+
|
|
5
48
|
## 0.10.0 - 2026-09-27
|
|
6
49
|
|
|
7
50
|
### 中文
|
|
@@ -9,7 +9,8 @@ export declare class AntigravityEngineAdapter implements IEngineAdapter {
|
|
|
9
9
|
private readonly defaultModel;
|
|
10
10
|
private readonly auth;
|
|
11
11
|
private readonly logger;
|
|
12
|
-
|
|
12
|
+
private readonly baseDir;
|
|
13
|
+
constructor(client: AntigravityAppClient, defaultModel?: string, auth?: AntigravityAuthManager, logger?: Logger, baseDir?: string);
|
|
13
14
|
listModels(): Promise<EngineModel[]>;
|
|
14
15
|
executeTurn(request: EngineTurnRequest): EngineTurnExecution;
|
|
15
16
|
preflightTurn(_request: EngineTurnRequest): Promise<void>;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { normalizeAgyModelId } from './client.js';
|
|
4
|
-
import { isCapacityOrUnavailableError, isVerificationError, isQuotaOrAuthError, } from './events.js';
|
|
4
|
+
import { isCapacityOrUnavailableError, isVerificationError, isQuotaOrAuthError, isTransientNetworkError, } from './events.js';
|
|
5
5
|
import { buildAttachmentPrompt } from '../telegram/media.js';
|
|
6
|
+
import { AntigravitySubagentTracker } from './subagents.js';
|
|
6
7
|
function extractToolSummary(name, parameters) {
|
|
7
8
|
if (!parameters)
|
|
8
9
|
return undefined;
|
|
@@ -39,11 +40,13 @@ export class AntigravityEngineAdapter {
|
|
|
39
40
|
defaultModel;
|
|
40
41
|
auth;
|
|
41
42
|
logger;
|
|
42
|
-
|
|
43
|
+
baseDir;
|
|
44
|
+
constructor(client, defaultModel = 'gemini-3.8-flash', auth, logger, baseDir) {
|
|
43
45
|
this.client = client;
|
|
44
46
|
this.defaultModel = defaultModel;
|
|
45
47
|
this.auth = auth;
|
|
46
48
|
this.logger = logger;
|
|
49
|
+
this.baseDir = baseDir ?? auth?.authDir;
|
|
47
50
|
}
|
|
48
51
|
async listModels() {
|
|
49
52
|
const rawModels = await this.client.listModels();
|
|
@@ -98,7 +101,67 @@ export class AntigravityEngineAdapter {
|
|
|
98
101
|
model: targetModel,
|
|
99
102
|
effort: request.effort ?? null,
|
|
100
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;
|
|
101
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
|
+
}
|
|
102
165
|
switch (ev.kind) {
|
|
103
166
|
case 'text':
|
|
104
167
|
emitter.emit('delta', ev.delta);
|
|
@@ -114,6 +177,7 @@ export class AntigravityEngineAdapter {
|
|
|
114
177
|
});
|
|
115
178
|
break;
|
|
116
179
|
case 'result': {
|
|
180
|
+
stopSubagentPolling();
|
|
117
181
|
const res = {
|
|
118
182
|
kind: 'result',
|
|
119
183
|
status: ev.status === 'SUCCESS' ? 'SUCCESS' : 'ERROR',
|
|
@@ -135,33 +199,45 @@ export class AntigravityEngineAdapter {
|
|
|
135
199
|
}
|
|
136
200
|
});
|
|
137
201
|
agTurn.on('error', (err) => {
|
|
202
|
+
stopSubagentPolling();
|
|
138
203
|
emitter.emit('error', err);
|
|
139
204
|
});
|
|
140
205
|
agTurn.on('exit', (code) => {
|
|
206
|
+
stopSubagentPolling();
|
|
141
207
|
emitter.emit('exit', code);
|
|
142
208
|
});
|
|
143
209
|
return {
|
|
144
|
-
turnId
|
|
145
|
-
|
|
210
|
+
get turnId() {
|
|
211
|
+
return agTurn.conversationId ?? undefined;
|
|
212
|
+
},
|
|
213
|
+
cancel: () => {
|
|
214
|
+
stopSubagentPolling();
|
|
215
|
+
agTurn.cancel();
|
|
216
|
+
},
|
|
146
217
|
waitForResult: async () => {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
+
}
|
|
165
241
|
},
|
|
166
242
|
on: (event, listener) => {
|
|
167
243
|
emitter.on(event, listener);
|
|
@@ -195,17 +271,22 @@ export class AntigravityEngineAdapter {
|
|
|
195
271
|
}
|
|
196
272
|
const rotated = await this.auth.rotateNextCandidate(failingName);
|
|
197
273
|
const nextAccountName = rotated.account.email || rotated.account.name;
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
+
}
|
|
209
290
|
await ctx.retryTurn(ctx.retryCount + 1);
|
|
210
291
|
return true;
|
|
211
292
|
}
|
|
@@ -224,9 +305,14 @@ export class AntigravityEngineAdapter {
|
|
|
224
305
|
this.auth.markCooldown(active.email, 30 * 60 * 1000);
|
|
225
306
|
}
|
|
226
307
|
const rotated = await this.auth.rotateNextCandidate(active?.name);
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
+
}
|
|
230
316
|
await ctx.retryTurn(ctx.retryCount + 1);
|
|
231
317
|
return true;
|
|
232
318
|
}
|
|
@@ -238,17 +324,69 @@ export class AntigravityEngineAdapter {
|
|
|
238
324
|
}
|
|
239
325
|
const isCapacity = isCapacityOrUnavailableError(ctx.error);
|
|
240
326
|
if (isCapacity && ctx.retryCount < 2) {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
+
}
|
|
244
335
|
await new Promise((r) => setTimeout(r, 2000));
|
|
245
336
|
await ctx.retryTurn(ctx.retryCount + 1);
|
|
246
337
|
return true;
|
|
247
338
|
}
|
|
248
339
|
if (isCapacity) {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
+
}
|
|
252
390
|
return true;
|
|
253
391
|
}
|
|
254
392
|
return false;
|
|
@@ -119,3 +119,8 @@ export declare class AntigravityAuthManager {
|
|
|
119
119
|
private atomicCopy;
|
|
120
120
|
private atomicWriteJson;
|
|
121
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;
|
package/dist/antigravity/auth.js
CHANGED
|
@@ -894,3 +894,40 @@ export class AntigravityAuthManager {
|
|
|
894
894
|
await fs.rename(tempPath, destPath);
|
|
895
895
|
}
|
|
896
896
|
}
|
|
897
|
+
/**
|
|
898
|
+
* Links Antigravity OAuth tokens and paused account state from sourceAuthDir into targetAuthDir.
|
|
899
|
+
* Does NOT link conversation_summaries.db or brain/ to keep conversation history isolated per bot.
|
|
900
|
+
*/
|
|
901
|
+
export function linkAntigravityAuthTokens(sourceAuthDir, targetAuthDir) {
|
|
902
|
+
const resolvedSource = path.resolve(sourceAuthDir);
|
|
903
|
+
const resolvedTarget = path.resolve(targetAuthDir);
|
|
904
|
+
if (resolvedSource === resolvedTarget)
|
|
905
|
+
return;
|
|
906
|
+
fsSync.mkdirSync(resolvedTarget, { recursive: true, mode: 0o700 });
|
|
907
|
+
if (!fsSync.existsSync(resolvedSource))
|
|
908
|
+
return;
|
|
909
|
+
const entries = fsSync.readdirSync(resolvedSource, { withFileTypes: true });
|
|
910
|
+
for (const entry of entries) {
|
|
911
|
+
if (entry.isFile() &&
|
|
912
|
+
(entry.name === 'antigravity-oauth-token' ||
|
|
913
|
+
entry.name.startsWith('antigravity-oauth-token_') ||
|
|
914
|
+
entry.name === 'paused-accounts.json')) {
|
|
915
|
+
const sourceFile = path.join(resolvedSource, entry.name);
|
|
916
|
+
const targetFile = path.join(resolvedTarget, entry.name);
|
|
917
|
+
try {
|
|
918
|
+
if (fsSync.existsSync(targetFile) || fsSync.lstatSync(targetFile, { throwIfNoEntry: false })) {
|
|
919
|
+
fsSync.unlinkSync(targetFile);
|
|
920
|
+
}
|
|
921
|
+
fsSync.symlinkSync(sourceFile, targetFile);
|
|
922
|
+
}
|
|
923
|
+
catch {
|
|
924
|
+
try {
|
|
925
|
+
fsSync.copyFileSync(sourceFile, targetFile);
|
|
926
|
+
}
|
|
927
|
+
catch {
|
|
928
|
+
// best-effort fallback
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|
|
@@ -183,8 +183,14 @@ export class AntigravityAppClient {
|
|
|
183
183
|
conversationId: activeConversationId,
|
|
184
184
|
stderr: stderrBuffer ? stderrBuffer.slice(-200) : undefined,
|
|
185
185
|
});
|
|
186
|
-
if (!finalResultEvent
|
|
187
|
-
const
|
|
186
|
+
if (!finalResultEvent) {
|
|
187
|
+
const isCanceled = signal === 'SIGINT' || signal === 'SIGTERM';
|
|
188
|
+
const errorMsg = isCanceled
|
|
189
|
+
? 'Execution canceled'
|
|
190
|
+
: (stderrBuffer.trim() ||
|
|
191
|
+
(code !== 0
|
|
192
|
+
? `Process exited with code ${code}${signal ? ` (${signal})` : ''}`
|
|
193
|
+
: 'Process exited unexpectedly before producing a result'));
|
|
188
194
|
const errResult = {
|
|
189
195
|
kind: 'result',
|
|
190
196
|
conversationId: activeConversationId || '',
|
|
@@ -12,6 +12,7 @@ import type { CodexAppClient, CodexAppServerRuntimeStatus } from '../codex_app/c
|
|
|
12
12
|
import { OpencodeEngineAdapter } from '../opencode/adapter.js';
|
|
13
13
|
import type { OpencodeAppClient } from '../opencode/client.js';
|
|
14
14
|
import { BridgeSessionCore } from '../controller/controller.js';
|
|
15
|
+
import type { SelfUpdateRuntime } from '../update.js';
|
|
15
16
|
export interface UnifiedBridgeRuntimeStatus {
|
|
16
17
|
running: boolean;
|
|
17
18
|
connected: boolean;
|
|
@@ -33,6 +34,7 @@ export interface UnifiedBridgeCoreOptions {
|
|
|
33
34
|
opencodeApp?: OpencodeAppClient | undefined;
|
|
34
35
|
opencodeAdapter?: OpencodeEngineAdapter | undefined;
|
|
35
36
|
defaultBackendId?: 'antigravity' | 'codex' | 'opencode' | string | undefined;
|
|
37
|
+
selfUpdater?: SelfUpdateRuntime | undefined;
|
|
36
38
|
}
|
|
37
39
|
export declare class UnifiedBridgeCore {
|
|
38
40
|
private readonly config;
|
|
@@ -49,6 +51,7 @@ export declare class UnifiedBridgeCore {
|
|
|
49
51
|
readonly codexCore?: BridgeSessionCore | undefined;
|
|
50
52
|
private readonly opencodeAdapter?;
|
|
51
53
|
private readonly defaultBackendId;
|
|
54
|
+
private readonly selfUpdater?;
|
|
52
55
|
private readonly orchestrator;
|
|
53
56
|
private readonly watchers;
|
|
54
57
|
private readonly pendingAgyRenames;
|
|
@@ -62,6 +65,11 @@ export declare class UnifiedBridgeCore {
|
|
|
62
65
|
startTelegramPolling(): Promise<void>;
|
|
63
66
|
start(): Promise<void>;
|
|
64
67
|
stop(): Promise<void>;
|
|
68
|
+
private selfUpdatePollTimer;
|
|
69
|
+
private scheduleSelfUpdateStatusPoll;
|
|
70
|
+
private clearSelfUpdateStatusPoll;
|
|
71
|
+
private ownsScope;
|
|
72
|
+
private pollSelfUpdateStatus;
|
|
65
73
|
getRuntimeStatus(): UnifiedBridgeRuntimeStatus;
|
|
66
74
|
isIdleForServiceUpdate(): boolean;
|
|
67
75
|
getCurrentAuthLabel(): Promise<string | null>;
|
|
@@ -87,6 +95,8 @@ export declare class UnifiedBridgeCore {
|
|
|
87
95
|
private watchConversation;
|
|
88
96
|
private unwatchConversation;
|
|
89
97
|
private scheduleWatcherPoll;
|
|
98
|
+
private getScopeLocale;
|
|
99
|
+
private renderWatcherProgressUpdate;
|
|
90
100
|
private pollWatcher;
|
|
91
101
|
private handleWatcherTranscriptEntry;
|
|
92
102
|
private sendAuthMenu;
|