@foxden-app/foxclaw 0.10.0 → 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.
- package/.env.example +9 -8
- package/CHANGELOG.md +25 -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 +5 -0
- package/dist/antigravity/controller.js +113 -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/dist/opencode/adapter.js
CHANGED
|
@@ -720,7 +720,7 @@ export class OpencodeBridgeCore {
|
|
|
720
720
|
const model = index >= 0 ? models[index] : models.find((item) => `${item.providerId}/${item.modelId}` === normalized || item.modelId === normalized || storeModel(item.providerId, item.modelId) === normalized);
|
|
721
721
|
if (!model)
|
|
722
722
|
throw new Error(localize(locale, `未知模型:${raw}`, `Unknown model: ${raw}`));
|
|
723
|
-
this.store.
|
|
723
|
+
this.store.setChatModel(scopeId, storeModel(model.providerId, model.modelId));
|
|
724
724
|
const prefs = readPrefs(this.store.getChatSettings(scopeId));
|
|
725
725
|
if (prefs.variant && !model.variants.includes(prefs.variant))
|
|
726
726
|
this.writePrefs(scopeId, { ...prefs, variant: null });
|
|
@@ -1792,7 +1792,7 @@ export class OpencodeBridgeCore {
|
|
|
1792
1792
|
}
|
|
1793
1793
|
else {
|
|
1794
1794
|
const parsed = parseStoredModel(action.value);
|
|
1795
|
-
this.store.
|
|
1795
|
+
this.store.setChatModel(event.scopeId, action.value);
|
|
1796
1796
|
this.writePrefs(event.scopeId, { ...readPrefs(this.store.getChatSettings(event.scopeId)), variant: null });
|
|
1797
1797
|
answer = `${parsed.providerId}/${parsed.modelId}`;
|
|
1798
1798
|
}
|
package/dist/store/database.d.ts
CHANGED
|
@@ -59,6 +59,7 @@ export declare class BridgeStore {
|
|
|
59
59
|
scopeId: string;
|
|
60
60
|
chatId: string;
|
|
61
61
|
} | null;
|
|
62
|
+
migrateTelegramUnnamespacedScope(botId: string, chatId: string): void;
|
|
62
63
|
getBinding(chatId: string): ThreadBinding | null;
|
|
63
64
|
setBinding(chatId: string, threadId: string, cwd: string | null): void;
|
|
64
65
|
listBindings(): ThreadBinding[];
|
|
@@ -71,6 +72,8 @@ export declare class BridgeStore {
|
|
|
71
72
|
}>;
|
|
72
73
|
getChatSettings(chatId: string): ChatSessionSettings | null;
|
|
73
74
|
setChatSettings(chatId: string, model: string | null, reasoningEffort: ReasoningEffortValue | null, locale?: AppLocale | null): void;
|
|
75
|
+
setChatModel(chatId: string, model: string | null): void;
|
|
76
|
+
setChatEffort(chatId: string, effort: ReasoningEffortValue | null): void;
|
|
74
77
|
setChatLocale(chatId: string, locale: AppLocale): void;
|
|
75
78
|
setChatAccessPreset(chatId: string, accessPreset: AccessPresetValue | null): void;
|
|
76
79
|
setChatCollaborationMode(chatId: string, collaborationMode: CollaborationModeValue | null): void;
|
package/dist/store/database.js
CHANGED
|
@@ -298,6 +298,31 @@ export class BridgeStore {
|
|
|
298
298
|
const row = this.db.prepare('SELECT scope_id, chat_id FROM telegram_private_scopes WHERE bot_id = ?').get(botId);
|
|
299
299
|
return row ? { scopeId: String(row.scope_id), chatId: String(row.chat_id) } : null;
|
|
300
300
|
}
|
|
301
|
+
migrateTelegramUnnamespacedScope(botId, chatId) {
|
|
302
|
+
const legacyScope = `telegram:${chatId}::root`;
|
|
303
|
+
const namespacedScope = `telegram:${botId}:${chatId}::root`;
|
|
304
|
+
const existing = this.db.prepare('SELECT chat_id, thread_id, cwd, updated_at FROM chat_bindings WHERE chat_id = ?').get(legacyScope);
|
|
305
|
+
if (existing) {
|
|
306
|
+
const target = this.db.prepare('SELECT chat_id, thread_id, cwd, updated_at FROM chat_bindings WHERE chat_id = ?').get(namespacedScope);
|
|
307
|
+
if (!target) {
|
|
308
|
+
this.db.prepare('INSERT INTO chat_bindings (chat_id, thread_id, cwd, updated_at) VALUES (?, ?, ?, ?)').run(namespacedScope, existing.thread_id, existing.cwd, existing.updated_at);
|
|
309
|
+
}
|
|
310
|
+
else if (Number(existing.updated_at) > Number(target.updated_at)) {
|
|
311
|
+
this.db.prepare('UPDATE chat_bindings SET thread_id = ?, cwd = ?, updated_at = ? WHERE chat_id = ?').run(existing.thread_id, existing.cwd, existing.updated_at, namespacedScope);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
const legacyBackends = this.db.prepare('SELECT * FROM scope_backend_bindings WHERE scope_id = ?').all(legacyScope);
|
|
315
|
+
for (const row of legacyBackends) {
|
|
316
|
+
const backendId = String(row.backend_id);
|
|
317
|
+
const targetRow = this.db.prepare('SELECT updated_at FROM scope_backend_bindings WHERE scope_id = ? AND backend_id = ?').get(namespacedScope, backendId);
|
|
318
|
+
if (!targetRow) {
|
|
319
|
+
this.db.prepare('INSERT INTO scope_backend_bindings (scope_id, backend_id, thread_id, cwd, updated_at, model, reasoning_effort, active_turn_message_mode, service_tier, access_preset) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)').run(namespacedScope, backendId, row.thread_id, row.cwd, row.updated_at, row.model, row.reasoning_effort, row.active_turn_message_mode, row.service_tier, row.access_preset);
|
|
320
|
+
}
|
|
321
|
+
else if (Number(row.updated_at) > Number(targetRow.updated_at)) {
|
|
322
|
+
this.db.prepare('UPDATE scope_backend_bindings SET thread_id = ?, cwd = ?, updated_at = ?, model = ?, reasoning_effort = ?, active_turn_message_mode = ?, service_tier = ?, access_preset = ? WHERE scope_id = ? AND backend_id = ?').run(row.thread_id, row.cwd, row.updated_at, row.model, row.reasoning_effort, row.active_turn_message_mode, row.service_tier, row.access_preset, namespacedScope, backendId);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
301
326
|
getBinding(chatId) {
|
|
302
327
|
const row = this.db.prepare('SELECT chat_id, thread_id, cwd, updated_at FROM chat_bindings WHERE chat_id = ?').get(chatId);
|
|
303
328
|
if (!row)
|
|
@@ -370,6 +395,14 @@ export class BridgeStore {
|
|
|
370
395
|
const nextLocale = locale === undefined ? current?.locale ?? null : locale;
|
|
371
396
|
this.writeChatSettings(chatId, model, reasoningEffort, nextLocale, current?.accessPreset ?? null, current?.collaborationMode ?? null, current?.serviceTier ?? null, current?.activeTurnMessageMode ?? null, current?.activeBackendId ?? null);
|
|
372
397
|
}
|
|
398
|
+
setChatModel(chatId, model) {
|
|
399
|
+
const current = this.getChatSettings(chatId);
|
|
400
|
+
this.setChatSettings(chatId, model, current?.reasoningEffort ?? null);
|
|
401
|
+
}
|
|
402
|
+
setChatEffort(chatId, effort) {
|
|
403
|
+
const current = this.getChatSettings(chatId);
|
|
404
|
+
this.setChatSettings(chatId, current?.model ?? null, effort);
|
|
405
|
+
}
|
|
373
406
|
setChatLocale(chatId, locale) {
|
|
374
407
|
const current = this.getChatSettings(chatId);
|
|
375
408
|
this.writeChatSettings(chatId, current?.model ?? null, current?.reasoningEffort ?? null, locale, current?.accessPreset ?? null, current?.collaborationMode ?? null, current?.serviceTier ?? null, current?.activeTurnMessageMode ?? null, current?.activeBackendId ?? null);
|
package/dist/telegram/api.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export interface TelegramRemoteFile {
|
|
|
9
9
|
file_size?: number;
|
|
10
10
|
file_path?: string;
|
|
11
11
|
}
|
|
12
|
+
export declare function isTransientNetworkError(err: unknown): boolean;
|
|
12
13
|
export declare function callTelegramApi<T>(botToken: string, method: string, body: Record<string, unknown>): Promise<TelegramApiResult<T>>;
|
|
13
14
|
export declare function callTelegramMultipartApi<T>(botToken: string, method: string, fields: Record<string, string>, files: Array<{
|
|
14
15
|
fieldName: string;
|
package/dist/telegram/api.js
CHANGED
|
@@ -4,16 +4,28 @@ import https from 'node:https';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { pipeline } from 'node:stream/promises';
|
|
6
6
|
const DEFAULT_API_BASE_URL = 'https://api.telegram.org';
|
|
7
|
-
function isTransientNetworkError(err) {
|
|
7
|
+
export function isTransientNetworkError(err) {
|
|
8
8
|
if (!err)
|
|
9
9
|
return false;
|
|
10
|
-
const msg = String(err.message || err);
|
|
11
|
-
return (msg.includes('
|
|
12
|
-
msg.includes('
|
|
13
|
-
msg.includes('
|
|
14
|
-
msg.includes('
|
|
15
|
-
msg.includes('
|
|
16
|
-
msg.includes('
|
|
10
|
+
const msg = String(err.message || err).toLowerCase();
|
|
11
|
+
return (msg.includes('client network socket disconnected') ||
|
|
12
|
+
msg.includes('econnreset') ||
|
|
13
|
+
msg.includes('etimedout') ||
|
|
14
|
+
msg.includes('eai_again') ||
|
|
15
|
+
msg.includes('enotfound') ||
|
|
16
|
+
msg.includes('econnrefused') ||
|
|
17
|
+
msg.includes('socket hang up') ||
|
|
18
|
+
msg.includes('timed out') ||
|
|
19
|
+
msg.includes('timeout') ||
|
|
20
|
+
msg.includes('network socket disconnected') ||
|
|
21
|
+
msg.includes('network error') ||
|
|
22
|
+
msg.includes('fetch failed') ||
|
|
23
|
+
msg.includes('und_err_') ||
|
|
24
|
+
msg.includes('read tcp') ||
|
|
25
|
+
msg.includes('connection timed out') ||
|
|
26
|
+
msg.includes('broken pipe') ||
|
|
27
|
+
msg.includes('ehostunreach') ||
|
|
28
|
+
msg.includes('enetunreach'));
|
|
17
29
|
}
|
|
18
30
|
function callTelegramApiOnce(botToken, method, body) {
|
|
19
31
|
const payload = JSON.stringify(body);
|
|
@@ -51,7 +63,7 @@ function callTelegramApiOnce(botToken, method, body) {
|
|
|
51
63
|
});
|
|
52
64
|
}
|
|
53
65
|
export async function callTelegramApi(botToken, method, body) {
|
|
54
|
-
const maxAttempts = method === 'getUpdates' ? 1 :
|
|
66
|
+
const maxAttempts = method === 'getUpdates' ? 1 : 4;
|
|
55
67
|
let lastError;
|
|
56
68
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
57
69
|
try {
|
|
@@ -60,7 +72,7 @@ export async function callTelegramApi(botToken, method, body) {
|
|
|
60
72
|
catch (err) {
|
|
61
73
|
lastError = err;
|
|
62
74
|
if (attempt < maxAttempts && isTransientNetworkError(err)) {
|
|
63
|
-
await new Promise((r) => setTimeout(r,
|
|
75
|
+
await new Promise((r) => setTimeout(r, Math.min(3000, 500 * Math.pow(2, attempt - 1))));
|
|
64
76
|
continue;
|
|
65
77
|
}
|
|
66
78
|
throw err;
|
|
@@ -68,7 +80,7 @@ export async function callTelegramApi(botToken, method, body) {
|
|
|
68
80
|
}
|
|
69
81
|
throw lastError;
|
|
70
82
|
}
|
|
71
|
-
|
|
83
|
+
function callTelegramMultipartApiOnce(botToken, method, fields, files) {
|
|
72
84
|
const boundary = `foxclaw-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
73
85
|
const parts = [];
|
|
74
86
|
for (const [name, value] of Object.entries(fields)) {
|
|
@@ -114,6 +126,24 @@ export async function callTelegramMultipartApi(botToken, method, fields, files)
|
|
|
114
126
|
request.end();
|
|
115
127
|
});
|
|
116
128
|
}
|
|
129
|
+
export async function callTelegramMultipartApi(botToken, method, fields, files) {
|
|
130
|
+
const maxAttempts = 3;
|
|
131
|
+
let lastError;
|
|
132
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
133
|
+
try {
|
|
134
|
+
return await callTelegramMultipartApiOnce(botToken, method, fields, files);
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
lastError = err;
|
|
138
|
+
if (attempt < maxAttempts && isTransientNetworkError(err)) {
|
|
139
|
+
await new Promise((r) => setTimeout(r, Math.min(3000, 500 * Math.pow(2, attempt - 1))));
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
throw err;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
throw lastError;
|
|
146
|
+
}
|
|
117
147
|
export async function getTelegramFile(botToken, fileId) {
|
|
118
148
|
const result = await callTelegramApi(botToken, 'getFile', { file_id: fileId });
|
|
119
149
|
if (!result.ok || !result.result) {
|
|
@@ -121,7 +151,7 @@ export async function getTelegramFile(botToken, fileId) {
|
|
|
121
151
|
}
|
|
122
152
|
return result.result;
|
|
123
153
|
}
|
|
124
|
-
|
|
154
|
+
async function downloadTelegramFileOnce(botToken, remoteFilePath, destinationPath) {
|
|
125
155
|
await fs.promises.mkdir(path.dirname(destinationPath), { recursive: true });
|
|
126
156
|
const tempPath = `${destinationPath}.tmp-${process.pid}-${Date.now()}`;
|
|
127
157
|
let response = null;
|
|
@@ -158,6 +188,24 @@ export async function downloadTelegramFile(botToken, remoteFilePath, destination
|
|
|
158
188
|
response?.destroy();
|
|
159
189
|
}
|
|
160
190
|
}
|
|
191
|
+
export async function downloadTelegramFile(botToken, remoteFilePath, destinationPath) {
|
|
192
|
+
const maxAttempts = 3;
|
|
193
|
+
let lastError;
|
|
194
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
195
|
+
try {
|
|
196
|
+
return await downloadTelegramFileOnce(botToken, remoteFilePath, destinationPath);
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
lastError = err;
|
|
200
|
+
if (attempt < maxAttempts && isTransientNetworkError(err)) {
|
|
201
|
+
await new Promise((r) => setTimeout(r, Math.min(3000, 500 * Math.pow(2, attempt - 1))));
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
throw err;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
throw lastError;
|
|
208
|
+
}
|
|
161
209
|
function escapeMultipartName(value) {
|
|
162
210
|
return value.replaceAll('\\', '\\\\').replaceAll('"', '\\"').replaceAll('\r', '').replaceAll('\n', '');
|
|
163
211
|
}
|
|
@@ -5,16 +5,17 @@ const ID_FILE = '.foxclaw-bot.json';
|
|
|
5
5
|
export function prepareTelegramBotHome(baseDir, botId, username, sharedHome = null) {
|
|
6
6
|
if (!/^bot\d+$/.test(botId))
|
|
7
7
|
throw new Error('Invalid Telegram bot identity');
|
|
8
|
-
|
|
8
|
+
const normalizedUsername = username ? username.replace(/^@/, '') : null;
|
|
9
|
+
if (normalizedUsername !== null && !/^[A-Za-z0-9_]{1,64}$/.test(normalizedUsername))
|
|
9
10
|
throw new Error('Invalid Telegram username for directory');
|
|
10
|
-
|
|
11
|
-
fs.
|
|
11
|
+
fs.mkdirSync(baseDir, { recursive: true, mode: 0o700 });
|
|
12
|
+
const base = fs.realpathSync(path.resolve(baseDir));
|
|
12
13
|
const legacy = path.join(base, botId);
|
|
13
14
|
const legacyStat = lstat(legacy);
|
|
14
15
|
const source = legacyStat?.isSymbolicLink() ? fs.realpathSync(legacy) : legacy;
|
|
15
16
|
if (path.dirname(source) !== base)
|
|
16
17
|
throw new Error(`Bot directory points outside its storage root: ${legacy}`);
|
|
17
|
-
const target =
|
|
18
|
+
const target = normalizedUsername ? path.join(base, `@${normalizedUsername}`) : source;
|
|
18
19
|
const sourceStat = lstat(source);
|
|
19
20
|
const targetStat = lstat(target);
|
|
20
21
|
if (sourceStat && !sourceStat.isDirectory())
|
package/dist/types.d.ts
CHANGED
|
@@ -403,7 +403,9 @@ export interface RuntimeStatus {
|
|
|
403
403
|
activeTurns: number;
|
|
404
404
|
runtimeKind?: 'default' | 'isolated';
|
|
405
405
|
currentAuth?: string | null;
|
|
406
|
+
defaultBackend?: 'codex' | 'antigravity';
|
|
406
407
|
codexHome?: string | null;
|
|
408
|
+
antigravityHome?: string | null;
|
|
407
409
|
codexAppServer?: RuntimeStatus['codexAppServer'];
|
|
408
410
|
}>;
|
|
409
411
|
weixinRuntime?: {
|
package/dist/update.js
CHANGED
|
@@ -33,7 +33,23 @@ export function inferPnpmFallbackPackageFromEntryPoint(entryPoint) {
|
|
|
33
33
|
return 'pnpm@latest';
|
|
34
34
|
}
|
|
35
35
|
export function resolveSelfUpdateInstaller(entryPoint, nodePath = process.execPath, exists = fs.existsSync, env = process.env) {
|
|
36
|
-
|
|
36
|
+
let pnpmHome = inferPnpmHomeFromEntryPoint(entryPoint);
|
|
37
|
+
const normalizedEntryPoint = entryPoint.replace(/\\/g, '/');
|
|
38
|
+
const isExplicitNpm = normalizedEntryPoint.includes('/node_modules/@foxden-app/foxclaw/')
|
|
39
|
+
&& !normalizedEntryPoint.includes('/.pnpm/')
|
|
40
|
+
&& !normalizedEntryPoint.includes('/global/');
|
|
41
|
+
if (!pnpmHome && !isExplicitNpm) {
|
|
42
|
+
const candidatePnpmHome = env.PNPM_HOME?.trim() || (process.platform === 'win32'
|
|
43
|
+
? (env.LOCALAPPDATA ? path.join(env.LOCALAPPDATA, 'pnpm') : null)
|
|
44
|
+
: path.join(os.homedir(), '.local', 'share', 'pnpm'));
|
|
45
|
+
if (candidatePnpmHome && exists(candidatePnpmHome)) {
|
|
46
|
+
const foxclawBin = path.join(candidatePnpmHome, process.platform === 'win32' ? 'foxclaw.cmd' : 'foxclaw');
|
|
47
|
+
const globalDir = path.join(candidatePnpmHome, 'global');
|
|
48
|
+
if (exists(foxclawBin) || exists(globalDir)) {
|
|
49
|
+
pnpmHome = candidatePnpmHome;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
37
53
|
if (pnpmHome) {
|
|
38
54
|
const npmCommandName = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
39
55
|
const npmCommand = executableCandidates(npmCommandName, nodePath, env)
|
package/package.json
CHANGED