@foxden-app/foxclaw 0.7.2 → 0.10.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 +14 -0
- package/CHANGELOG.md +129 -0
- package/README.md +22 -2
- package/README_EN.md +22 -2
- package/dist/antigravity/adapter.d.ts +17 -0
- package/dist/antigravity/adapter.js +256 -0
- package/dist/antigravity/auth.d.ts +121 -0
- package/dist/antigravity/auth.js +896 -0
- package/dist/antigravity/client.d.ts +32 -0
- package/dist/antigravity/client.js +238 -0
- package/dist/antigravity/controller.d.ts +96 -0
- package/dist/antigravity/controller.js +1922 -0
- package/dist/antigravity/conversations.d.ts +29 -0
- package/dist/antigravity/conversations.js +289 -0
- package/dist/antigravity/events.d.ts +101 -0
- package/dist/antigravity/events.js +114 -0
- package/dist/antigravity/runtime.d.ts +47 -0
- package/dist/antigravity/runtime.js +82 -0
- package/dist/auth/cross_node_sync.js +2 -2
- package/dist/channels/telegram/telegram_channel_adapter.d.ts +10 -3
- package/dist/channels/telegram/telegram_channel_adapter.js +2 -1
- package/dist/channels/weixin/weixin_channel_adapter.d.ts +5 -1
- package/dist/codex_app/adapter.d.ts +20 -0
- package/dist/codex_app/adapter.js +213 -0
- package/dist/codex_app/client.d.ts +2 -1
- package/dist/codex_app/client.js +47 -4
- package/dist/codex_app/force_takeover.d.ts +12 -0
- package/dist/codex_app/force_takeover.js +27 -0
- package/dist/config.d.ts +12 -0
- package/dist/config.js +42 -0
- package/dist/controller/controller.d.ts +15 -5
- package/dist/controller/controller.js +252 -20
- package/dist/core/attachments.d.ts +11 -0
- package/dist/core/attachments.js +56 -0
- package/dist/core/engine_spi.d.ts +76 -0
- package/dist/core/engine_spi.js +1 -0
- package/dist/core/orchestrator.d.ts +101 -0
- package/dist/core/orchestrator.js +1404 -0
- package/dist/core/stream_preview.d.ts +29 -0
- package/dist/core/stream_preview.js +146 -0
- package/dist/core/turn_queue.d.ts +19 -0
- package/dist/core/turn_queue.js +41 -0
- package/dist/i18n.d.ts +36 -6
- package/dist/i18n.js +81 -6
- package/dist/main.js +190 -20
- package/dist/opencode/adapter.d.ts +11 -0
- package/dist/opencode/adapter.js +168 -0
- package/dist/store/database.d.ts +41 -0
- package/dist/store/database.js +242 -43
- package/dist/store/token_usage.d.ts +23 -0
- package/dist/store/token_usage.js +61 -0
- package/dist/telegram/api.js +34 -1
- package/dist/telegram/bot_home.d.ts +3 -0
- package/dist/telegram/bot_home.js +107 -0
- package/dist/telegram/gateway.d.ts +5 -0
- package/dist/telegram/gateway.js +21 -0
- package/dist/types.d.ts +1 -0
- package/dist/update.d.ts +6 -0
- package/dist/update.js +78 -6
- package/dist/voice/target.js +2 -1
- package/dist/voice/tts.js +4 -2
- 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
- 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
- package/docs/user-manual.md +11 -1
- package/docs/zh/2026-09-05-bot-home-migration.md +26 -0
- package/docs/zh/2026-09-05-reliability-review.md +53 -0
- package/docs/zh/troubleshooting.md +12 -0
- package/docs/zh/user-manual.md +13 -1
- package/package.json +2 -2
- package/scripts/force-takeover.py +118 -0
- package/scripts/force-takeover.test.py +122 -0
package/dist/main.js
CHANGED
|
@@ -7,8 +7,9 @@ import process from 'node:process';
|
|
|
7
7
|
import { createInterface } from 'node:readline/promises';
|
|
8
8
|
import { spawnSync } from 'node:child_process';
|
|
9
9
|
import { fileURLToPath } from 'node:url';
|
|
10
|
-
import { APP_HOME, buildCodexApiProviderOverrides, DEFAULT_CODEX_TELEGRAM_HOME, DEFAULT_ENV_PATH, DEFAULT_LOG_PATH, DEFAULT_STATUS_PATH, getLoadedEnvPath, loadConfig, loadEnv, } from './config.js';
|
|
10
|
+
import { APP_HOME, buildCodexApiProviderOverrides, DEFAULT_CODEX_TELEGRAM_HOME, DEFAULT_ANTIGRAVITY_TELEGRAM_HOME, DEFAULT_ENV_PATH, DEFAULT_LOG_PATH, DEFAULT_STATUS_PATH, getLoadedEnvPath, loadConfig, loadEnv, } from './config.js';
|
|
11
11
|
import { createAuthRefreshNotificationAggregator, } from './auth/notifications.js';
|
|
12
|
+
import { getAntigravityTelegramCommands } from './i18n.js';
|
|
12
13
|
import { installBundledCodexSkills } from './codex_skills.js';
|
|
13
14
|
import { acquireProcessLock, LockHeldError } from './lock.js';
|
|
14
15
|
import { readRuntimeStatus, writeRuntimeStatus } from './runtime.js';
|
|
@@ -18,6 +19,7 @@ import { clearPendingClusterUpdateBroadcast, createSelfUpdateRuntime, inferPnpmH
|
|
|
18
19
|
import { TELEGRAM_VOICE_MAX_BYTES, TELEGRAM_VOICE_SUPPORTED_EXTENSIONS, telegramVoiceContentType, } from './voice/files.js';
|
|
19
20
|
import { inferTelegramBotId, resolveTelegramVoiceTarget } from './voice/target.js';
|
|
20
21
|
import { shouldShowRuntimeLastUpdate } from './update_status.js';
|
|
22
|
+
import { prepareTelegramBotHome } from './telegram/bot_home.js';
|
|
21
23
|
const rawCommand = process.argv[2];
|
|
22
24
|
const command = rawCommand || 'serve';
|
|
23
25
|
loadEnv();
|
|
@@ -225,6 +227,40 @@ async function main() {
|
|
|
225
227
|
}
|
|
226
228
|
return;
|
|
227
229
|
}
|
|
230
|
+
if (command === 'resume') {
|
|
231
|
+
const args = process.argv.slice(3);
|
|
232
|
+
const botIndex = args.indexOf('--bot-id');
|
|
233
|
+
const botId = botIndex >= 0 ? args[botIndex + 1] : null;
|
|
234
|
+
if (botIndex >= 0)
|
|
235
|
+
args.splice(botIndex, 2);
|
|
236
|
+
if ((botIndex >= 0 && !botId) || args.length > 1 || args[0]?.startsWith('-')) {
|
|
237
|
+
throw new Error('Usage: foxclaw resume [thread-id] [--bot-id <bot-id>]');
|
|
238
|
+
}
|
|
239
|
+
const status = readRuntimeStatus(process.env.STATUS_PATH || DEFAULT_STATUS_PATH);
|
|
240
|
+
const runtime = botId ? status?.bots?.find(bot => bot.id === botId) : status;
|
|
241
|
+
if (!runtime)
|
|
242
|
+
throw new Error('No matching runtime status. Check foxclaw status.');
|
|
243
|
+
if (!botId && new Set(status?.bots?.map(bot => bot.codexAppServer?.port).filter(Boolean)).size > 1) {
|
|
244
|
+
throw new Error('Multiple Codex servers found. Select one with --bot-id from foxclaw status --json.');
|
|
245
|
+
}
|
|
246
|
+
const server = runtime.codexAppServer;
|
|
247
|
+
if (!server?.running || !server.pid || !server.port) {
|
|
248
|
+
throw new Error('Codex app-server is not running. Check foxclaw status.');
|
|
249
|
+
}
|
|
250
|
+
try {
|
|
251
|
+
process.kill(server.pid, 0);
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
throw new Error('Codex app-server status is stale; the process has exited.');
|
|
255
|
+
}
|
|
256
|
+
const result = spawnSync(process.env.CODEX_CLI_BIN || 'codex', [
|
|
257
|
+
'resume', '--remote', `ws://127.0.0.1:${server.port}`, ...args,
|
|
258
|
+
], { stdio: 'inherit' });
|
|
259
|
+
if (result.error)
|
|
260
|
+
throw result.error;
|
|
261
|
+
process.exitCode = result.status ?? 1;
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
228
264
|
if (command === 'doctor') {
|
|
229
265
|
const failed = !runDoctorChecks();
|
|
230
266
|
process.exit(failed ? 1 : 0);
|
|
@@ -278,6 +314,7 @@ Usage:
|
|
|
278
314
|
foxclaw init
|
|
279
315
|
foxclaw doctor
|
|
280
316
|
foxclaw status
|
|
317
|
+
foxclaw resume [thread-id] [--bot-id <bot-id>]
|
|
281
318
|
foxclaw start|restart|stop
|
|
282
319
|
foxclaw update
|
|
283
320
|
foxclaw send-voice <path> [caption]
|
|
@@ -552,7 +589,7 @@ function truncateStatusLine(value, maxLength) {
|
|
|
552
589
|
return normalized.length <= maxLength ? normalized : `${normalized.slice(0, Math.max(0, maxLength - 1))}…`;
|
|
553
590
|
}
|
|
554
591
|
async function runServeCli() {
|
|
555
|
-
const [{ BridgeMessagingRouter }, { TelegramMessagingPort }, { WeixinChannelAdapter }, { WeixinMessagingPort }, { attachIlinkRuntimeFromBridgeLogger }, { loadWeixinAccount }, { Logger }, { BridgeStore }, { TelegramGateway }, { CodexAppClient }, { BridgeSessionCore }, { TelegramChannelAdapter }, { AuthCandidateMirror }, { CrossNodeAuthSync }, { OpencodeTelegramRuntime },] = await Promise.all([
|
|
592
|
+
const [{ BridgeMessagingRouter }, { TelegramMessagingPort }, { WeixinChannelAdapter }, { WeixinMessagingPort }, { attachIlinkRuntimeFromBridgeLogger }, { loadWeixinAccount }, { Logger }, { BridgeStore }, { TelegramGateway }, { CodexAppClient }, { BridgeSessionCore }, { TelegramChannelAdapter }, { AuthCandidateMirror }, { CrossNodeAuthSync }, { OpencodeTelegramRuntime }, { AntigravityTelegramRuntime }, { UnifiedBridgeCore }, { AntigravityAppClient }, { AntigravityAuthManager },] = await Promise.all([
|
|
556
593
|
import('./channels/bridge_messaging_router.js'),
|
|
557
594
|
import('./channels/telegram/telegram_messaging_port.js'),
|
|
558
595
|
import('./channels/weixin/weixin_channel_adapter.js'),
|
|
@@ -568,6 +605,10 @@ async function runServeCli() {
|
|
|
568
605
|
import('./auth/mirror.js'),
|
|
569
606
|
import('./auth/cross_node_sync.js'),
|
|
570
607
|
import('./opencode/runtime.js'),
|
|
608
|
+
import('./antigravity/runtime.js'),
|
|
609
|
+
import('./antigravity/controller.js'),
|
|
610
|
+
import('./antigravity/client.js'),
|
|
611
|
+
import('./antigravity/auth.js'),
|
|
571
612
|
]);
|
|
572
613
|
const config = loadConfig();
|
|
573
614
|
const logger = new Logger(config.logLevel, config.logPath);
|
|
@@ -583,26 +624,85 @@ async function runServeCli() {
|
|
|
583
624
|
let activeAuthMirror = null;
|
|
584
625
|
let activeAuthSync = null;
|
|
585
626
|
let activeOpencodeRuntime = null;
|
|
627
|
+
const activeAntigravityRuntimes = [];
|
|
628
|
+
let sharedCodexApp = null;
|
|
629
|
+
let sharedAntigravityApp = null;
|
|
630
|
+
let sharedAntigravityAuth = null;
|
|
586
631
|
try {
|
|
587
632
|
store = new BridgeStore(config.storePath);
|
|
633
|
+
sharedAntigravityAuth = new AntigravityAuthManager(config.antigravityAuthDir, logger);
|
|
634
|
+
sharedAntigravityApp = new AntigravityAppClient(config.antigravityCliBin, logger);
|
|
635
|
+
if (config.codexCliBin) {
|
|
636
|
+
sharedCodexApp = new CodexAppClient(config.codexCliBin, config.codexAppLaunchCmd, config.codexAppAutolaunch, config.codexAppServerStatePath, config.codexAppServerLogPath, logger, config.codexHome ? { CODEX_HOME: config.codexHome } : null, codexApiProviderOverrides);
|
|
637
|
+
}
|
|
588
638
|
if (config.opencodeBotToken) {
|
|
589
639
|
activeOpencodeRuntime = new OpencodeTelegramRuntime(config, store, logger);
|
|
590
640
|
await activeOpencodeRuntime.start();
|
|
591
641
|
logger.info('opencode.bridge.started', activeOpencodeRuntime.getRuntimeStatus());
|
|
592
642
|
}
|
|
643
|
+
if (config.antigravityBotTokens.length > 0) {
|
|
644
|
+
for (const token of config.antigravityBotTokens) {
|
|
645
|
+
const bot = new TelegramGateway(token, config.tgAllowedUserId, config.tgAllowedChatId, config.telegramPollIntervalMs, store, logger, true, getAntigravityTelegramCommands);
|
|
646
|
+
const id = await bot.initializeIdentity();
|
|
647
|
+
const username = await bot.resolveUsername();
|
|
648
|
+
const sharedDefaultRuntime = config.antigravityDefaultRuntimeBotToken
|
|
649
|
+
? config.antigravityDefaultRuntimeBotToken === token
|
|
650
|
+
: (token === config.antigravityBotTokens[0]);
|
|
651
|
+
let home;
|
|
652
|
+
let authDir;
|
|
653
|
+
let app;
|
|
654
|
+
let auth;
|
|
655
|
+
if (sharedDefaultRuntime) {
|
|
656
|
+
home = os.homedir();
|
|
657
|
+
authDir = config.antigravityAuthDir;
|
|
658
|
+
app = sharedAntigravityApp;
|
|
659
|
+
auth = sharedAntigravityAuth;
|
|
660
|
+
}
|
|
661
|
+
else {
|
|
662
|
+
home = prepareTelegramBotHome(DEFAULT_ANTIGRAVITY_TELEGRAM_HOME, id, username, null);
|
|
663
|
+
authDir = path.join(home, '.gemini', 'antigravity-cli');
|
|
664
|
+
fs.mkdirSync(authDir, { recursive: true, mode: 0o700 });
|
|
665
|
+
app = new AntigravityAppClient(config.antigravityCliBin, logger, { HOME: home });
|
|
666
|
+
auth = new AntigravityAuthManager(authDir, logger);
|
|
667
|
+
}
|
|
668
|
+
const runtimeConfig = {
|
|
669
|
+
...config,
|
|
670
|
+
antigravityBotToken: token,
|
|
671
|
+
antigravityBotTokens: [token],
|
|
672
|
+
tgScopeBotId: id,
|
|
673
|
+
antigravityAuthDir: authDir,
|
|
674
|
+
};
|
|
675
|
+
const runtime = new AntigravityTelegramRuntime(runtimeConfig, store, logger, {
|
|
676
|
+
botToken: token,
|
|
677
|
+
botId: id,
|
|
678
|
+
botUsername: username ?? undefined,
|
|
679
|
+
sharedDefaultRuntime,
|
|
680
|
+
home,
|
|
681
|
+
authDir,
|
|
682
|
+
codexApp: sharedCodexApp ?? undefined,
|
|
683
|
+
app,
|
|
684
|
+
auth,
|
|
685
|
+
});
|
|
686
|
+
await runtime.start();
|
|
687
|
+
activeAntigravityRuntimes.push(runtime);
|
|
688
|
+
logger.info('antigravity.bridge.started', runtime.getRuntimeStatus());
|
|
689
|
+
}
|
|
690
|
+
}
|
|
593
691
|
if (config.tgMultiBotMode) {
|
|
594
692
|
const seeds = [];
|
|
595
693
|
const canonicalAuthDir = config.codexAuthDir ?? config.codexHome ?? path.join(os.homedir(), '.codex');
|
|
596
|
-
|
|
694
|
+
const identities = await Promise.all(config.tgBotTokens.map(async (token) => {
|
|
597
695
|
const bot = new TelegramGateway(token, config.tgAllowedUserId, config.tgAllowedChatId, config.telegramPollIntervalMs, store, logger, true);
|
|
598
696
|
const id = await bot.initializeIdentity();
|
|
697
|
+
const username = await bot.resolveUsername();
|
|
698
|
+
return { token, bot, id, username };
|
|
699
|
+
}));
|
|
700
|
+
for (const { token, bot, id, username } of identities) {
|
|
599
701
|
if (seeds.some((runtime) => runtime.id === id)) {
|
|
600
702
|
throw new Error(`TG_BOT_TOKENS contains duplicate Telegram bot identity: ${id}`);
|
|
601
703
|
}
|
|
602
704
|
const sharedDefaultRuntime = config.tgDefaultRuntimeBotToken === token;
|
|
603
|
-
const home = sharedDefaultRuntime
|
|
604
|
-
? (config.codexHome ?? path.join(os.homedir(), '.codex'))
|
|
605
|
-
: path.join(DEFAULT_CODEX_TELEGRAM_HOME, id, 'home');
|
|
705
|
+
const home = prepareTelegramBotHome(DEFAULT_CODEX_TELEGRAM_HOME, id, username, sharedDefaultRuntime ? (config.codexHome ?? path.join(os.homedir(), '.codex')) : null);
|
|
606
706
|
const authDir = sharedDefaultRuntime ? canonicalAuthDir : home;
|
|
607
707
|
if (!sharedDefaultRuntime) {
|
|
608
708
|
fs.mkdirSync(home, { recursive: true, mode: 0o700 });
|
|
@@ -614,7 +714,7 @@ async function runServeCli() {
|
|
|
614
714
|
tgBotTokens: [token],
|
|
615
715
|
tgScopeBotId: id,
|
|
616
716
|
codexAuthDir: sharedDefaultRuntime ? config.codexAuthDir : home,
|
|
617
|
-
codexHome:
|
|
717
|
+
codexHome: home,
|
|
618
718
|
codexAppAutolaunch: sharedDefaultRuntime ? config.codexAppAutolaunch : false,
|
|
619
719
|
codexAppServerStatePath: sharedDefaultRuntime
|
|
620
720
|
? config.codexAppServerStatePath
|
|
@@ -623,9 +723,7 @@ async function runServeCli() {
|
|
|
623
723
|
? config.codexAppServerLogPath
|
|
624
724
|
: path.join(APP_HOME, 'logs', `codex-app-server-${id}.log`),
|
|
625
725
|
};
|
|
626
|
-
const childEnv =
|
|
627
|
-
? (config.codexHome ? { CODEX_HOME: config.codexHome } : null)
|
|
628
|
-
: { CODEX_HOME: home };
|
|
726
|
+
const childEnv = { CODEX_HOME: home };
|
|
629
727
|
const app = new CodexAppClient(runtimeConfig.codexCliBin, runtimeConfig.codexAppLaunchCmd, runtimeConfig.codexAppAutolaunch, runtimeConfig.codexAppServerStatePath, runtimeConfig.codexAppServerLogPath, logger, childEnv, [
|
|
630
728
|
...codexApiProviderOverrides,
|
|
631
729
|
...(sharedDefaultRuntime ? [] : ['cli_auth_credentials_store="file"']),
|
|
@@ -661,6 +759,7 @@ async function runServeCli() {
|
|
|
661
759
|
statusPath: config.statusPath,
|
|
662
760
|
logPath: path.join(APP_HOME, 'logs', 'update.log'),
|
|
663
761
|
codexCliBin: config.codexCliBin,
|
|
762
|
+
agyCliBin: config.antigravityCliBin,
|
|
664
763
|
});
|
|
665
764
|
const lastSelfUpdatePath = path.join(APP_HOME, 'runtime', 'last-self-update.json');
|
|
666
765
|
let lastSelfUpdate = readSelfUpdateStatus(lastSelfUpdatePath);
|
|
@@ -707,6 +806,20 @@ async function runServeCli() {
|
|
|
707
806
|
...(weixinStatus.codexAppServer ? { codexAppServer: weixinStatus.codexAppServer } : {}),
|
|
708
807
|
},
|
|
709
808
|
} : {}),
|
|
809
|
+
...(activeAntigravityRuntimes.length > 0 ? {
|
|
810
|
+
antigravityBots: activeAntigravityRuntimes.map((runtime) => {
|
|
811
|
+
const status = runtime.getRuntimeStatus();
|
|
812
|
+
return {
|
|
813
|
+
id: runtime.id,
|
|
814
|
+
username: status.botUsername ?? runtime.botGateway.username,
|
|
815
|
+
connected: running && Boolean(status.connected),
|
|
816
|
+
activeTurns: running ? (status.activeTurns ?? 0) : 0,
|
|
817
|
+
runtimeKind: runtime.isSharedDefaultRuntime ? 'default' : 'isolated',
|
|
818
|
+
home: runtime.botHome,
|
|
819
|
+
authDir: runtime.authDirectory,
|
|
820
|
+
};
|
|
821
|
+
}),
|
|
822
|
+
} : {}),
|
|
710
823
|
authMirror: mirror.getStatus(),
|
|
711
824
|
authSync: authSync?.getStatus() ?? null,
|
|
712
825
|
authProactiveRefresh,
|
|
@@ -803,7 +916,14 @@ async function runServeCli() {
|
|
|
803
916
|
for (const seed of seeds) {
|
|
804
917
|
const telegramMessaging = new TelegramMessagingPort(seed.bot);
|
|
805
918
|
const outbound = new BridgeMessagingRouter(telegramMessaging, null);
|
|
806
|
-
const
|
|
919
|
+
const codexCore = new BridgeSessionCore(seed.config, store, logger, seed.bot, seed.app, outbound, selfUpdater, coordinator, false);
|
|
920
|
+
const core = new UnifiedBridgeCore(seed.config, store, logger, seed.bot, sharedAntigravityApp ?? undefined, sharedAntigravityAuth ?? undefined, telegramMessaging, {
|
|
921
|
+
codexApp: seed.app,
|
|
922
|
+
codexCore,
|
|
923
|
+
antigravityApp: sharedAntigravityApp ?? undefined,
|
|
924
|
+
antigravityAuth: sharedAntigravityAuth ?? undefined,
|
|
925
|
+
defaultBackendId: 'codex',
|
|
926
|
+
});
|
|
807
927
|
runtimes.push({ ...seed, core, telegram: new TelegramChannelAdapter(core) });
|
|
808
928
|
}
|
|
809
929
|
if (config.wxEnabled) {
|
|
@@ -889,8 +1009,10 @@ async function runServeCli() {
|
|
|
889
1009
|
await activeWeixinCore?.stop();
|
|
890
1010
|
await Promise.all(runtimes.map((runtime) => runtime.telegram.stop()));
|
|
891
1011
|
await activeOpencodeRuntime?.stop();
|
|
1012
|
+
await Promise.all(activeAntigravityRuntimes.map((runtime) => runtime.stop().catch(() => { })));
|
|
892
1013
|
writeAggregateStatus(false);
|
|
893
|
-
|
|
1014
|
+
const allManaged = [...managedApps, ...(sharedCodexApp ? [sharedCodexApp] : [])];
|
|
1015
|
+
await Promise.all(allManaged.map((app) => app.stop({ terminateServer: true }).catch((error) => {
|
|
894
1016
|
logger.warn('codex.app-server.stop_failed', { error: serializeError(error) });
|
|
895
1017
|
})));
|
|
896
1018
|
store?.close();
|
|
@@ -904,12 +1026,13 @@ async function runServeCli() {
|
|
|
904
1026
|
const singleCodexHome = config.codexHome ?? path.join(os.homedir(), '.codex');
|
|
905
1027
|
installBundledCodexSkills(packageRoot, singleCodexHome);
|
|
906
1028
|
const bot = new TelegramGateway(config.tgBotToken, config.tgAllowedUserId, config.tgAllowedChatId, config.telegramPollIntervalMs, store, logger);
|
|
907
|
-
const app = new CodexAppClient(config.codexCliBin, config.codexAppLaunchCmd, config.codexAppAutolaunch, config.codexAppServerStatePath, config.codexAppServerLogPath, logger, config.codexHome ? { CODEX_HOME: config.codexHome } : null, codexApiProviderOverrides);
|
|
1029
|
+
const app = sharedCodexApp ?? new CodexAppClient(config.codexCliBin, config.codexAppLaunchCmd, config.codexAppAutolaunch, config.codexAppServerStatePath, config.codexAppServerLogPath, logger, config.codexHome ? { CODEX_HOME: config.codexHome } : null, codexApiProviderOverrides);
|
|
908
1030
|
const telegramMessaging = new TelegramMessagingPort(bot);
|
|
909
1031
|
const weixinMessaging = config.wxEnabled
|
|
910
1032
|
? new WeixinMessagingPort(store, (id) => loadWeixinAccount(config.weixinAccountsDir, id))
|
|
911
1033
|
: null;
|
|
912
1034
|
const outbound = new BridgeMessagingRouter(telegramMessaging, weixinMessaging);
|
|
1035
|
+
void outbound;
|
|
913
1036
|
const selfUpdater = createSelfUpdateRuntime({
|
|
914
1037
|
entryPoint,
|
|
915
1038
|
nodePath: process.execPath,
|
|
@@ -917,10 +1040,32 @@ async function runServeCli() {
|
|
|
917
1040
|
statusPath: config.statusPath,
|
|
918
1041
|
logPath: path.join(APP_HOME, 'logs', 'update.log'),
|
|
919
1042
|
codexCliBin: config.codexCliBin,
|
|
1043
|
+
agyCliBin: config.antigravityCliBin,
|
|
920
1044
|
});
|
|
921
1045
|
let singleAuthSync = null;
|
|
922
1046
|
let singleMirror = null;
|
|
923
1047
|
let core = null;
|
|
1048
|
+
const writeSingleStatus = (running = true) => {
|
|
1049
|
+
const coreStatus = core?.getRuntimeStatus();
|
|
1050
|
+
writeRuntimeStatus(config.statusPath, {
|
|
1051
|
+
running,
|
|
1052
|
+
connected: running && Boolean(coreStatus?.connected),
|
|
1053
|
+
userAgent: coreStatus?.userAgent ?? app.getUserAgent(),
|
|
1054
|
+
codexHome: coreStatus?.codexHome ?? singleCodexHome,
|
|
1055
|
+
...(coreStatus?.codexAppServer ? { codexAppServer: coreStatus.codexAppServer } : app.getServerStatus() ? { codexAppServer: app.getServerStatus() } : {}),
|
|
1056
|
+
botUsername: coreStatus?.botUsername ?? bot.username,
|
|
1057
|
+
currentBindings: store?.countBindings() ?? 0,
|
|
1058
|
+
pendingApprovals: store?.countPendingApprovals() ?? 0,
|
|
1059
|
+
pendingUserInputs: store?.countPendingUserInputs() ?? 0,
|
|
1060
|
+
queuedTurns: store?.countQueuedTurnInputs() ?? 0,
|
|
1061
|
+
activeTurns: coreStatus?.activeTurns ?? 0,
|
|
1062
|
+
lastError: null,
|
|
1063
|
+
updatedAt: new Date().toISOString(),
|
|
1064
|
+
channels: { telegram: running, weixin: running && config.wxEnabled },
|
|
1065
|
+
authMirror: singleMirror?.getStatus() ?? null,
|
|
1066
|
+
authSync: singleAuthSync?.getStatus() ?? null,
|
|
1067
|
+
});
|
|
1068
|
+
};
|
|
924
1069
|
const singleAuthDir = config.codexAuthDir ?? config.codexHome ?? process.env.CODEX_AUTH_DIR ?? path.join(os.homedir(), '.codex');
|
|
925
1070
|
const singleLocalAuthRefreshLease = createLocalAuthRefreshLease();
|
|
926
1071
|
const singleAuthSyncLocalIdle = () => Boolean(core?.isIdleForServiceUpdate())
|
|
@@ -973,12 +1118,9 @@ async function runServeCli() {
|
|
|
973
1118
|
authSyncPushAll: () => singleAuthSync?.pushAll() ?? Promise.resolve({ sent: 0, skipped: 0 }),
|
|
974
1119
|
authSyncTest: () => singleAuthSync?.testPeers() ?? Promise.resolve({ sent: 0, replied: 0, missing: [] }),
|
|
975
1120
|
authSyncAudit: () => singleAuthSync?.auditCluster() ?? Promise.resolve(null),
|
|
976
|
-
statusUpdated: (
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
authMirror: singleMirror?.getStatus() ?? null,
|
|
980
|
-
authSync: singleAuthSync?.getStatus() ?? null,
|
|
981
|
-
});
|
|
1121
|
+
statusUpdated: (_status) => {
|
|
1122
|
+
void _status;
|
|
1123
|
+
writeSingleStatus(true);
|
|
982
1124
|
},
|
|
983
1125
|
} : null;
|
|
984
1126
|
if (config.authSyncEnabled) {
|
|
@@ -1002,7 +1144,15 @@ async function runServeCli() {
|
|
|
1002
1144
|
await singleMirror.initialize();
|
|
1003
1145
|
activeAuthMirror = singleMirror;
|
|
1004
1146
|
}
|
|
1005
|
-
|
|
1147
|
+
const singleOutbound = new BridgeMessagingRouter(telegramMessaging, null);
|
|
1148
|
+
const singleCodexCore = new BridgeSessionCore(config, store, logger, bot, app, singleOutbound, selfUpdater, singleCoordinator, false);
|
|
1149
|
+
core = new UnifiedBridgeCore(config, store, logger, bot, sharedAntigravityApp ?? undefined, sharedAntigravityAuth ?? undefined, telegramMessaging, {
|
|
1150
|
+
codexApp: app,
|
|
1151
|
+
codexCore: singleCodexCore,
|
|
1152
|
+
antigravityApp: sharedAntigravityApp ?? undefined,
|
|
1153
|
+
antigravityAuth: sharedAntigravityAuth ?? undefined,
|
|
1154
|
+
defaultBackendId: 'codex',
|
|
1155
|
+
});
|
|
1006
1156
|
if (config.authSyncEnabled && singleMirror) {
|
|
1007
1157
|
await bot.initializeIdentity();
|
|
1008
1158
|
singleAuthSync = new CrossNodeAuthSync(buildAuthSyncConfig(config, bot.username ? `@${bot.username}` : 'default'), logger, {
|
|
@@ -1060,6 +1210,7 @@ async function runServeCli() {
|
|
|
1060
1210
|
if (weixinAdapter) {
|
|
1061
1211
|
await weixinAdapter.start();
|
|
1062
1212
|
}
|
|
1213
|
+
writeSingleStatus(true);
|
|
1063
1214
|
logger.info('bridge.started', core.getRuntimeStatus());
|
|
1064
1215
|
const shutdown = async (signal) => {
|
|
1065
1216
|
logger.info('bridge.shutting_down', { signal });
|
|
@@ -1069,6 +1220,7 @@ async function runServeCli() {
|
|
|
1069
1220
|
await weixinAdapter?.stop();
|
|
1070
1221
|
await telegram.stop();
|
|
1071
1222
|
await activeOpencodeRuntime?.stop();
|
|
1223
|
+
await Promise.all(activeAntigravityRuntimes.map((runtime) => runtime.stop().catch(() => { })));
|
|
1072
1224
|
writeRuntimeStatus(config.statusPath, {
|
|
1073
1225
|
running: false,
|
|
1074
1226
|
connected: false,
|
|
@@ -1103,6 +1255,7 @@ async function runServeCli() {
|
|
|
1103
1255
|
await activeWeixinCore?.stop().catch(() => { });
|
|
1104
1256
|
await Promise.allSettled(activeTelegramAdapters.map((adapter) => adapter.stop()));
|
|
1105
1257
|
await activeOpencodeRuntime?.stop().catch(() => { });
|
|
1258
|
+
await Promise.allSettled(activeAntigravityRuntimes.map((runtime) => runtime.stop()));
|
|
1106
1259
|
await Promise.allSettled(managedApps.map((app) => app.stop({ terminateServer: true })));
|
|
1107
1260
|
store?.close();
|
|
1108
1261
|
processLock.release();
|
|
@@ -1797,6 +1950,23 @@ function runDoctorChecks() {
|
|
|
1797
1950
|
].map((value) => value.trim()).filter(Boolean);
|
|
1798
1951
|
checks.push(['OpenCode uses an independent Telegram bot', !codexTokens.includes(process.env.OPENCODE_BOT_TOKEN.trim())]);
|
|
1799
1952
|
}
|
|
1953
|
+
const antigravityTokens = [
|
|
1954
|
+
...(process.env.ANTIGRAVITY_BOT_TOKENS ?? '').split(','),
|
|
1955
|
+
process.env.ANTIGRAVITY_BOT_TOKEN ?? '',
|
|
1956
|
+
].map((value) => value.trim()).filter(Boolean);
|
|
1957
|
+
if (antigravityTokens.length > 0) {
|
|
1958
|
+
const configuredAgyBin = process.env.ANTIGRAVITY_CLI_BIN;
|
|
1959
|
+
checks.push(['antigravity (agy) cli available', hasConfiguredCommand(configuredAgyBin, 'agy')]);
|
|
1960
|
+
const codexTokens = [
|
|
1961
|
+
...(process.env.TG_BOT_TOKENS ?? '').split(','),
|
|
1962
|
+
process.env.TG_BOT_TOKEN ?? '',
|
|
1963
|
+
].map((value) => value.trim()).filter(Boolean);
|
|
1964
|
+
const hasConflict = antigravityTokens.some((t) => codexTokens.includes(t));
|
|
1965
|
+
checks.push(['Antigravity uses independent Telegram bot(s)', !hasConflict]);
|
|
1966
|
+
const authDir = process.env.ANTIGRAVITY_AUTH_DIR || path.join(os.homedir(), '.gemini', 'antigravity-cli');
|
|
1967
|
+
const hasAuth = fs.existsSync(path.join(authDir, 'antigravity-oauth-token'));
|
|
1968
|
+
checks.push(['antigravity oauth token available', hasAuth]);
|
|
1969
|
+
}
|
|
1800
1970
|
if (process.env.WX_ENABLED === 'true' || process.env.WX_ENABLED === '1') {
|
|
1801
1971
|
const accountsDir = process.env.WEIXIN_ACCOUNTS_DIR || path.join(APP_HOME, 'weixin', 'accounts');
|
|
1802
1972
|
let hasAccounts = false;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { IEngineAdapter, EngineModel, EngineTurnRequest, EngineTurnExecution } from '../core/engine_spi.js';
|
|
2
|
+
import type { OpencodeAppClient } from './client.js';
|
|
3
|
+
export declare class OpencodeEngineAdapter implements IEngineAdapter {
|
|
4
|
+
private readonly client;
|
|
5
|
+
private readonly defaultModel?;
|
|
6
|
+
readonly id = "opencode";
|
|
7
|
+
readonly name = "OpenCode (SDK)";
|
|
8
|
+
constructor(client: OpencodeAppClient, defaultModel?: string | undefined);
|
|
9
|
+
listModels(): Promise<EngineModel[]>;
|
|
10
|
+
executeTurn(request: EngineTurnRequest): EngineTurnExecution;
|
|
11
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { formatSdkError } from './client.js';
|
|
3
|
+
import { buildAttachmentPrompt } from '../telegram/media.js';
|
|
4
|
+
export class OpencodeEngineAdapter {
|
|
5
|
+
client;
|
|
6
|
+
defaultModel;
|
|
7
|
+
id = 'opencode';
|
|
8
|
+
name = 'OpenCode (SDK)';
|
|
9
|
+
constructor(client, defaultModel) {
|
|
10
|
+
this.client = client;
|
|
11
|
+
this.defaultModel = defaultModel;
|
|
12
|
+
}
|
|
13
|
+
async listModels() {
|
|
14
|
+
if (!this.client.isConnected())
|
|
15
|
+
return [];
|
|
16
|
+
try {
|
|
17
|
+
const response = await this.client.getClient().provider.list();
|
|
18
|
+
if (response.error || !response.data)
|
|
19
|
+
return [];
|
|
20
|
+
const { all, connected, default: defaultMap } = response.data;
|
|
21
|
+
const models = [];
|
|
22
|
+
for (const providerId of connected) {
|
|
23
|
+
const provider = all.find((p) => p.id === providerId);
|
|
24
|
+
if (!provider)
|
|
25
|
+
continue;
|
|
26
|
+
const defaultForProvider = defaultMap[providerId];
|
|
27
|
+
for (const m of Object.values(provider.models)) {
|
|
28
|
+
const modelFullId = `${providerId}/${m.id}`;
|
|
29
|
+
models.push({
|
|
30
|
+
id: modelFullId,
|
|
31
|
+
name: `${provider.name} - ${m.name}`,
|
|
32
|
+
isDefault: m.id === defaultForProvider,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return models;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
executeTurn(request) {
|
|
43
|
+
const emitter = new EventEmitter();
|
|
44
|
+
let sessionId = request.threadId;
|
|
45
|
+
let cancelled = false;
|
|
46
|
+
let promptText = request.prompt;
|
|
47
|
+
if (request.stagedAttachments && request.stagedAttachments.length > 0 && !request.prompt.includes('Telegram attachments:')) {
|
|
48
|
+
promptText = buildAttachmentPrompt(request.prompt, request.stagedAttachments);
|
|
49
|
+
}
|
|
50
|
+
let finalResult = null;
|
|
51
|
+
let turnError = null;
|
|
52
|
+
const resultWaiters = [];
|
|
53
|
+
const resolveWaiters = (res) => {
|
|
54
|
+
while (resultWaiters.length > 0) {
|
|
55
|
+
resultWaiters.shift()(res);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const run = async () => {
|
|
59
|
+
try {
|
|
60
|
+
const sdk = this.client.getClient();
|
|
61
|
+
if (!sessionId) {
|
|
62
|
+
const createRes = await sdk.session.create({ directory: request.cwd });
|
|
63
|
+
if (createRes.error || !createRes.data) {
|
|
64
|
+
throw new Error(`Failed to create OpenCode session: ${formatSdkError(createRes.error)}`);
|
|
65
|
+
}
|
|
66
|
+
sessionId = createRes.data.id;
|
|
67
|
+
}
|
|
68
|
+
if (cancelled)
|
|
69
|
+
return;
|
|
70
|
+
let accumulatedResponse = '';
|
|
71
|
+
const onEvent = (ev) => {
|
|
72
|
+
if ('sessionId' in ev && ev.sessionId === sessionId) {
|
|
73
|
+
if (ev.kind === 'text') {
|
|
74
|
+
if (ev.delta) {
|
|
75
|
+
accumulatedResponse += ev.delta;
|
|
76
|
+
emitter.emit('delta', ev.delta);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else if (ev.kind === 'tool') {
|
|
80
|
+
emitter.emit('tool', {
|
|
81
|
+
name: ev.tool,
|
|
82
|
+
args: undefined,
|
|
83
|
+
output: undefined,
|
|
84
|
+
status: ev.status === 'running' ? 'running' : ev.status === 'error' ? 'failed' : 'completed',
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
else if (ev.kind === 'idle') {
|
|
88
|
+
cleanup();
|
|
89
|
+
finalResult = {
|
|
90
|
+
kind: 'result',
|
|
91
|
+
status: 'SUCCESS',
|
|
92
|
+
response: accumulatedResponse,
|
|
93
|
+
conversationId: sessionId,
|
|
94
|
+
};
|
|
95
|
+
emitter.emit('result', finalResult);
|
|
96
|
+
resolveWaiters(finalResult);
|
|
97
|
+
}
|
|
98
|
+
else if (ev.kind === 'error') {
|
|
99
|
+
cleanup();
|
|
100
|
+
turnError = new Error(ev.message);
|
|
101
|
+
finalResult = {
|
|
102
|
+
kind: 'result',
|
|
103
|
+
status: 'ERROR',
|
|
104
|
+
response: ev.message,
|
|
105
|
+
conversationId: sessionId,
|
|
106
|
+
};
|
|
107
|
+
emitter.emit('result', finalResult);
|
|
108
|
+
emitter.emit('error', turnError);
|
|
109
|
+
resolveWaiters(finalResult);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
const cleanup = () => {
|
|
114
|
+
this.client.off('event', onEvent);
|
|
115
|
+
};
|
|
116
|
+
this.client.on('event', onEvent);
|
|
117
|
+
let modelParam;
|
|
118
|
+
const targetModel = request.model || this.defaultModel;
|
|
119
|
+
if (targetModel && targetModel.includes('/')) {
|
|
120
|
+
const [pId, ...mRest] = targetModel.split('/');
|
|
121
|
+
modelParam = { providerID: pId, modelID: mRest.join('/') };
|
|
122
|
+
}
|
|
123
|
+
const promptRes = await sdk.session.promptAsync({
|
|
124
|
+
sessionID: sessionId,
|
|
125
|
+
directory: request.cwd,
|
|
126
|
+
parts: [{ type: 'text', text: promptText }],
|
|
127
|
+
...(modelParam ? { model: modelParam } : {}),
|
|
128
|
+
});
|
|
129
|
+
if (promptRes.error) {
|
|
130
|
+
cleanup();
|
|
131
|
+
turnError = new Error(formatSdkError(promptRes.error));
|
|
132
|
+
emitter.emit('error', turnError);
|
|
133
|
+
resolveWaiters(null);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
this.client.watchSessionUntilIdle(sessionId, request.cwd);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
turnError = err instanceof Error ? err : new Error(String(err));
|
|
141
|
+
emitter.emit('error', turnError);
|
|
142
|
+
resolveWaiters(null);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
void run();
|
|
146
|
+
return {
|
|
147
|
+
turnId: sessionId ?? undefined,
|
|
148
|
+
cancel: () => {
|
|
149
|
+
cancelled = true;
|
|
150
|
+
if (sessionId) {
|
|
151
|
+
void this.client.getClient().session.abort({ sessionID: sessionId }).catch(() => { });
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
waitForResult: async () => {
|
|
155
|
+
if (finalResult)
|
|
156
|
+
return finalResult;
|
|
157
|
+
if (turnError)
|
|
158
|
+
return null;
|
|
159
|
+
return new Promise((resolve) => {
|
|
160
|
+
resultWaiters.push(resolve);
|
|
161
|
+
});
|
|
162
|
+
},
|
|
163
|
+
on: (event, listener) => {
|
|
164
|
+
emitter.on(event, listener);
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
package/dist/store/database.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AccessPresetValue, ActiveTurnMessageMode, AppLocale, CachedThread, ChatSessionSettings, CollaborationModeValue, GuidedPlanSessionRecord, GuidedPlanSessionState, PendingAttachmentBatchRecord, PendingAttachmentBatchStatus, PendingApprovalRecord, QueuedTurnInputRecord, QueuedTurnInputStatus, ReasoningEffortValue, ThreadBinding } from '../types.js';
|
|
2
|
+
import type { CumulativeTokenTotals, CumulativeTokenUsageRecord, TokenUsageDelta } from './token_usage.js';
|
|
2
3
|
export interface ActiveTurnPreviewRecord {
|
|
3
4
|
turnId: string;
|
|
4
5
|
scopeId: string;
|
|
@@ -62,6 +63,12 @@ export declare class BridgeStore {
|
|
|
62
63
|
setBinding(chatId: string, threadId: string, cwd: string | null): void;
|
|
63
64
|
listBindings(): ThreadBinding[];
|
|
64
65
|
clearBinding(chatId: string): void;
|
|
66
|
+
getWatchedThread(chatId: string): string | null;
|
|
67
|
+
setWatchedThread(chatId: string, threadId: string | null): void;
|
|
68
|
+
listWatchedThreads(): Array<{
|
|
69
|
+
scopeId: string;
|
|
70
|
+
threadId: string;
|
|
71
|
+
}>;
|
|
65
72
|
getChatSettings(chatId: string): ChatSessionSettings | null;
|
|
66
73
|
setChatSettings(chatId: string, model: string | null, reasoningEffort: ReasoningEffortValue | null, locale?: AppLocale | null): void;
|
|
67
74
|
setChatLocale(chatId: string, locale: AppLocale): void;
|
|
@@ -69,6 +76,30 @@ export declare class BridgeStore {
|
|
|
69
76
|
setChatCollaborationMode(chatId: string, collaborationMode: CollaborationModeValue | null): void;
|
|
70
77
|
setChatServiceTier(chatId: string, serviceTier: string | null): void;
|
|
71
78
|
setChatActiveTurnMessageMode(chatId: string, activeTurnMessageMode: ActiveTurnMessageMode | null): void;
|
|
79
|
+
getActiveBackend(chatId: string): string | null;
|
|
80
|
+
setActiveBackend(chatId: string, backendId: string | null): void;
|
|
81
|
+
getScopeBackendBinding(scopeId: string, backendId: string): {
|
|
82
|
+
threadId: string;
|
|
83
|
+
cwd: string | null;
|
|
84
|
+
model?: string | null;
|
|
85
|
+
reasoningEffort?: string | null;
|
|
86
|
+
activeTurnMessageMode?: string | null;
|
|
87
|
+
serviceTier?: string | null;
|
|
88
|
+
accessPreset?: string | null;
|
|
89
|
+
} | null;
|
|
90
|
+
setScopeBackendBinding(scopeId: string, backendId: string, threadId: string, cwd: string | null, settings?: {
|
|
91
|
+
model?: string | null;
|
|
92
|
+
reasoningEffort?: string | null;
|
|
93
|
+
activeTurnMessageMode?: string | null;
|
|
94
|
+
serviceTier?: string | null;
|
|
95
|
+
accessPreset?: string | null;
|
|
96
|
+
} | null): void;
|
|
97
|
+
listScopeBackendBindings(scopeId: string): Array<{
|
|
98
|
+
backendId: string;
|
|
99
|
+
threadId: string;
|
|
100
|
+
cwd: string | null;
|
|
101
|
+
updatedAt: number;
|
|
102
|
+
}>;
|
|
72
103
|
findChatIdByThreadId(threadId: string): string | null;
|
|
73
104
|
findAllChatIdsByThreadId(threadId: string): string[];
|
|
74
105
|
countBindings(): number;
|
|
@@ -141,5 +172,15 @@ export declare class BridgeStore {
|
|
|
141
172
|
getCodexAuthPoolStats(): CodexAuthPoolStats;
|
|
142
173
|
setCodexAuthQuotaSnapshot(runtimeId: string, candidateName: string, accountId: string, quotaIdentityId: string, snapshot: Pick<CodexAuthQuotaSnapshotRecord, 'capturedAtMs' | 'planType' | 'primaryWindowDurationMins' | 'primaryRemainingPercent' | 'secondaryWindowDurationMins' | 'secondaryRemainingPercent'> & Partial<Pick<CodexAuthQuotaSnapshotRecord, 'primaryResetsAt' | 'secondaryResetsAt'>>): void;
|
|
143
174
|
listCodexAuthQuotaSnapshots(quotaIdentityIds: string[]): CodexAuthQuotaSnapshotRecord[];
|
|
175
|
+
recordTokenUsage(delta: TokenUsageDelta, backendId?: string): void;
|
|
176
|
+
setBackendCumulativeTokenUsage(usage: {
|
|
177
|
+
inputTokens: number;
|
|
178
|
+
outputTokens: number;
|
|
179
|
+
cachedTokens?: number;
|
|
180
|
+
totalTokens?: number;
|
|
181
|
+
turnsCount?: number;
|
|
182
|
+
}, backendId: string): void;
|
|
183
|
+
getCumulativeTokenUsage(backendId?: string): CumulativeTokenTotals;
|
|
184
|
+
getAllBackendTokenUsages(): CumulativeTokenUsageRecord[];
|
|
144
185
|
private ensureColumn;
|
|
145
186
|
}
|