@foxden-app/foxclaw 0.3.17 → 0.3.19
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 +5 -2
- package/README.md +14 -4
- package/README_EN.md +14 -4
- package/dist/auth/mirror.d.ts +25 -0
- package/dist/auth/mirror.js +199 -0
- package/dist/codex_app/client.d.ts +2 -1
- package/dist/codex_app/client.js +10 -2
- package/dist/codex_app/local_usage.d.ts +7 -6
- package/dist/codex_app/local_usage.js +55 -66
- package/dist/config.d.ts +7 -0
- package/dist/config.js +18 -1
- package/dist/controller/controller.d.ts +14 -2
- package/dist/controller/controller.js +110 -34
- package/dist/core/bridge_scope.d.ts +5 -2
- package/dist/core/bridge_scope.js +7 -3
- package/dist/i18n.d.ts +4 -4
- package/dist/i18n.js +4 -4
- package/dist/main.js +144 -9
- package/dist/store/database.d.ts +4 -2
- package/dist/store/database.js +42 -6
- package/dist/telegram/addressing.d.ts +1 -0
- package/dist/telegram/addressing.js +3 -0
- package/dist/telegram/gateway.d.ts +4 -1
- package/dist/telegram/gateway.js +23 -5
- package/dist/types.d.ts +7 -0
- package/dist/update.d.ts +5 -0
- package/dist/update.js +93 -5
- package/docs/agent-assisted-install.md +4 -4
- package/docs/install-for-beginners.md +3 -3
- package/docs/troubleshooting.md +1 -1
- package/docs/user-manual.md +11 -11
- package/docs/zh/agent-assisted-install.md +4 -4
- package/docs/zh/foxclaw-skill.md +1 -1
- package/docs/zh/install-for-beginners.md +3 -3
- package/docs/zh/troubleshooting.md +1 -1
- package/docs/zh/user-manual.md +11 -11
- package/package.json +1 -1
- package/skills/foxclaw/SKILL.md +26 -19
- package/skills/foxclaw/references/telegram-setup.md +5 -4
- package/skills/foxclaw/scripts/bootstrap_host.py +11 -8
- package/skills/foxclaw/scripts/bootstrap_remote.py +8 -4
- package/skills/npm-publish/SKILL.md +3 -0
package/dist/config.d.ts
CHANGED
|
@@ -7,12 +7,17 @@ export declare const DEFAULT_LOG_PATH: string;
|
|
|
7
7
|
export declare const DEFAULT_LOCK_PATH: string;
|
|
8
8
|
export declare const DEFAULT_CODEX_APP_SERVER_STATE_PATH: string;
|
|
9
9
|
export declare const DEFAULT_CODEX_APP_SERVER_LOG_PATH: string;
|
|
10
|
+
export declare const DEFAULT_CODEX_TELEGRAM_HOME: string;
|
|
10
11
|
export declare const DEFAULT_ENV_PATH: string;
|
|
11
12
|
export declare function resolveEnvPath(): string;
|
|
12
13
|
export declare function getLoadedEnvPath(): string | null;
|
|
13
14
|
export declare function loadEnv(): void;
|
|
14
15
|
export interface AppConfig {
|
|
15
16
|
tgBotToken: string;
|
|
17
|
+
tgBotTokens: string[];
|
|
18
|
+
tgMultiBotMode: boolean;
|
|
19
|
+
tgScopeBotId: string | null;
|
|
20
|
+
tgRequireExplicitGroupAddressing: boolean;
|
|
16
21
|
tgAllowedUserId: string;
|
|
17
22
|
tgAllowedChatId: string | null;
|
|
18
23
|
tgAllowedTopicId: number | null;
|
|
@@ -21,6 +26,8 @@ export interface AppConfig {
|
|
|
21
26
|
codexAppLaunchCmd: string;
|
|
22
27
|
codexAppServerStatePath: string;
|
|
23
28
|
codexAppServerLogPath: string;
|
|
29
|
+
codexAuthDir: string | null;
|
|
30
|
+
codexHome: string | null;
|
|
24
31
|
codexAppSyncOnOpen: boolean;
|
|
25
32
|
codexAppSyncOnTurnComplete: boolean;
|
|
26
33
|
storePath: string;
|
package/dist/config.js
CHANGED
|
@@ -10,6 +10,7 @@ export const DEFAULT_LOG_PATH = path.join(APP_HOME, 'logs', 'service.log');
|
|
|
10
10
|
export const DEFAULT_LOCK_PATH = path.join(APP_HOME, 'runtime', 'bridge.lock');
|
|
11
11
|
export const DEFAULT_CODEX_APP_SERVER_STATE_PATH = path.join(APP_HOME, 'runtime', 'codex-app-server.json');
|
|
12
12
|
export const DEFAULT_CODEX_APP_SERVER_LOG_PATH = path.join(APP_HOME, 'logs', 'codex-app-server.log');
|
|
13
|
+
export const DEFAULT_CODEX_TELEGRAM_HOME = path.join(APP_HOME, 'codex', 'telegram');
|
|
13
14
|
export const DEFAULT_ENV_PATH = path.join(APP_HOME, '.env');
|
|
14
15
|
let envLoaded = false;
|
|
15
16
|
let loadedEnvPath = null;
|
|
@@ -37,8 +38,22 @@ export function loadEnv() {
|
|
|
37
38
|
}
|
|
38
39
|
export function loadConfig() {
|
|
39
40
|
loadEnv();
|
|
41
|
+
const configuredTokens = parseCommaSeparatedIds(process.env.TG_BOT_TOKENS);
|
|
42
|
+
const legacyToken = optional('TG_BOT_TOKEN');
|
|
43
|
+
const tgBotTokens = configuredTokens.length > 0
|
|
44
|
+
? configuredTokens
|
|
45
|
+
: legacyToken
|
|
46
|
+
? [legacyToken]
|
|
47
|
+
: [];
|
|
48
|
+
if (tgBotTokens.length === 0) {
|
|
49
|
+
throw new Error('TG_BOT_TOKENS or TG_BOT_TOKEN is required');
|
|
50
|
+
}
|
|
40
51
|
const config = {
|
|
41
|
-
tgBotToken:
|
|
52
|
+
tgBotToken: tgBotTokens[0],
|
|
53
|
+
tgBotTokens,
|
|
54
|
+
tgMultiBotMode: configuredTokens.length > 0,
|
|
55
|
+
tgScopeBotId: null,
|
|
56
|
+
tgRequireExplicitGroupAddressing: configuredTokens.length > 1,
|
|
42
57
|
tgAllowedUserId: required('TG_ALLOWED_USER_ID'),
|
|
43
58
|
tgAllowedChatId: optional('TG_ALLOWED_CHAT_ID'),
|
|
44
59
|
tgAllowedTopicId: nullableIntEnv('TG_ALLOWED_TOPIC_ID'),
|
|
@@ -47,6 +62,8 @@ export function loadConfig() {
|
|
|
47
62
|
codexAppLaunchCmd: process.env.CODEX_APP_LAUNCH_CMD || 'codex app',
|
|
48
63
|
codexAppServerStatePath: process.env.CODEX_APP_SERVER_STATE_PATH || DEFAULT_CODEX_APP_SERVER_STATE_PATH,
|
|
49
64
|
codexAppServerLogPath: process.env.CODEX_APP_SERVER_LOG_PATH || DEFAULT_CODEX_APP_SERVER_LOG_PATH,
|
|
65
|
+
codexAuthDir: process.env.CODEX_AUTH_DIR?.trim() || null,
|
|
66
|
+
codexHome: process.env.CODEX_HOME?.trim() || null,
|
|
50
67
|
codexAppSyncOnOpen: boolEnv('CODEX_APP_SYNC_ON_OPEN', true),
|
|
51
68
|
codexAppSyncOnTurnComplete: boolEnv('CODEX_APP_SYNC_ON_TURN_COMPLETE', false),
|
|
52
69
|
storePath: process.env.STORE_PATH || DEFAULT_STORE_PATH,
|
|
@@ -6,6 +6,11 @@ import type { TelegramGateway, TelegramTextEvent } from '../telegram/gateway.js'
|
|
|
6
6
|
import { BridgeMessagingRouter } from '../channels/bridge_messaging_router.js';
|
|
7
7
|
import type { CodexAppClient } from '../codex_app/client.js';
|
|
8
8
|
import type { SelfUpdateRuntime } from '../update.js';
|
|
9
|
+
export interface CoreCoordinator {
|
|
10
|
+
canSelfUpdate?: () => boolean;
|
|
11
|
+
authCandidateUpdated?: (runtimeId: string, candidateName: string) => Promise<void>;
|
|
12
|
+
statusUpdated?: (status: RuntimeStatus) => void;
|
|
13
|
+
}
|
|
9
14
|
export declare class BridgeSessionCore {
|
|
10
15
|
private readonly config;
|
|
11
16
|
private readonly store;
|
|
@@ -13,6 +18,7 @@ export declare class BridgeSessionCore {
|
|
|
13
18
|
private readonly bot;
|
|
14
19
|
private readonly app;
|
|
15
20
|
private readonly selfUpdater;
|
|
21
|
+
private readonly coordinator;
|
|
16
22
|
private activeTurns;
|
|
17
23
|
private activeTurnsByTurnId;
|
|
18
24
|
private observedThreadWatchers;
|
|
@@ -51,7 +57,7 @@ export declare class BridgeSessionCore {
|
|
|
51
57
|
/** Last threads-panel pagination state per scope (Telegram inline nav + /open index alignment). */
|
|
52
58
|
private threadListPresentationState;
|
|
53
59
|
private readonly messaging;
|
|
54
|
-
constructor(config: AppConfig, store: BridgeStore, logger: Logger, bot: TelegramGateway, app: CodexAppClient, outbound: BridgeMessagingRouter, selfUpdater?: SelfUpdateRuntime | null);
|
|
60
|
+
constructor(config: AppConfig, store: BridgeStore, logger: Logger, bot: TelegramGateway, app: CodexAppClient, outbound: BridgeMessagingRouter, selfUpdater?: SelfUpdateRuntime | null, coordinator?: CoreCoordinator | null);
|
|
55
61
|
/** Wire Telegram inbound events. Call before {@link startCodexApp}. */
|
|
56
62
|
registerTelegramInboundHandlers(): void;
|
|
57
63
|
/**
|
|
@@ -140,6 +146,10 @@ export declare class BridgeSessionCore {
|
|
|
140
146
|
private stageAttachments;
|
|
141
147
|
private registerActiveTurn;
|
|
142
148
|
private createActiveTurnState;
|
|
149
|
+
isIdleForServiceUpdate(): boolean;
|
|
150
|
+
private hasLocalBlockingActivity;
|
|
151
|
+
private authRuntimeId;
|
|
152
|
+
private ownsScope;
|
|
143
153
|
private setActiveTurn;
|
|
144
154
|
private getActiveTurn;
|
|
145
155
|
private getActiveTurnsForTurn;
|
|
@@ -261,13 +271,14 @@ export declare class BridgeSessionCore {
|
|
|
261
271
|
private retryTurnAfterAuthRotation;
|
|
262
272
|
private selectNextCodexAuthCandidate;
|
|
263
273
|
private listCodexAuthState;
|
|
274
|
+
private resolveAuthDir;
|
|
264
275
|
private readCodexAuthSwitchLabels;
|
|
265
276
|
private codexAuthSwitchParams;
|
|
266
277
|
private switchCodexAuthAndRestart;
|
|
267
278
|
private buildNativeCollaborationMode;
|
|
268
279
|
private buildCodexUsageStatusLines;
|
|
269
280
|
private buildCodexLocalUsageStatusLines;
|
|
270
|
-
private
|
|
281
|
+
private formatCodexLocalResponseThroughputStatusLines;
|
|
271
282
|
private resolveFastStatusLabel;
|
|
272
283
|
private readCachedCodexLocalUsageStats;
|
|
273
284
|
private refreshCodexLocalUsageIfNeeded;
|
|
@@ -277,6 +288,7 @@ export declare class BridgeSessionCore {
|
|
|
277
288
|
private readCodexAuthQuotaSnapshots;
|
|
278
289
|
private writeCodexAuthQuotaSnapshots;
|
|
279
290
|
private codexAuthQuotaSnapshotPath;
|
|
291
|
+
private runtimeSnapshotFilename;
|
|
280
292
|
private sendThreadContextSummary;
|
|
281
293
|
private handleModelCommand;
|
|
282
294
|
private handleEffortCommand;
|
|
@@ -93,6 +93,7 @@ export class BridgeSessionCore {
|
|
|
93
93
|
bot;
|
|
94
94
|
app;
|
|
95
95
|
selfUpdater;
|
|
96
|
+
coordinator;
|
|
96
97
|
activeTurns = new Map();
|
|
97
98
|
activeTurnsByTurnId = new Map();
|
|
98
99
|
observedThreadWatchers = new Map();
|
|
@@ -131,13 +132,14 @@ export class BridgeSessionCore {
|
|
|
131
132
|
/** Last threads-panel pagination state per scope (Telegram inline nav + /open index alignment). */
|
|
132
133
|
threadListPresentationState = new Map();
|
|
133
134
|
messaging;
|
|
134
|
-
constructor(config, store, logger, bot, app, outbound, selfUpdater = null) {
|
|
135
|
+
constructor(config, store, logger, bot, app, outbound, selfUpdater = null, coordinator = null) {
|
|
135
136
|
this.config = config;
|
|
136
137
|
this.store = store;
|
|
137
138
|
this.logger = logger;
|
|
138
139
|
this.bot = bot;
|
|
139
140
|
this.app = app;
|
|
140
141
|
this.selfUpdater = selfUpdater;
|
|
142
|
+
this.coordinator = coordinator;
|
|
141
143
|
this.messaging = outbound;
|
|
142
144
|
}
|
|
143
145
|
/** Wire Telegram inbound events. Call before {@link startCodexApp}. */
|
|
@@ -323,6 +325,7 @@ export class BridgeSessionCore {
|
|
|
323
325
|
allowedChatId: this.config.tgAllowedChatId,
|
|
324
326
|
allowedTopicId: this.config.tgAllowedTopicId,
|
|
325
327
|
topicId: event.topicId,
|
|
328
|
+
requireExplicitGroupAddressing: this.config.tgRequireExplicitGroupAddressing,
|
|
326
329
|
}),
|
|
327
330
|
replyToBot: event.replyToBot,
|
|
328
331
|
});
|
|
@@ -1563,6 +1566,16 @@ export class BridgeSessionCore {
|
|
|
1563
1566
|
return;
|
|
1564
1567
|
}
|
|
1565
1568
|
const lines = [t(locale, 'auth_add_done', { value: pendingAuthAdd.name })];
|
|
1569
|
+
try {
|
|
1570
|
+
await this.coordinator?.authCandidateUpdated?.(this.authRuntimeId(), pendingAuthAdd.name);
|
|
1571
|
+
}
|
|
1572
|
+
catch (error) {
|
|
1573
|
+
this.logger.warn('codex.auth_candidate_sync_failed', {
|
|
1574
|
+
candidate: pendingAuthAdd.name,
|
|
1575
|
+
runtimeId: this.authRuntimeId(),
|
|
1576
|
+
error: toErrorMeta(error),
|
|
1577
|
+
});
|
|
1578
|
+
}
|
|
1566
1579
|
lines.push(...await this.buildCodexUsageStatusLines(locale));
|
|
1567
1580
|
await this.sendMessage(scopeId, lines.join('\n'));
|
|
1568
1581
|
return;
|
|
@@ -1572,7 +1585,7 @@ export class BridgeSessionCore {
|
|
|
1572
1585
|
: t(locale, 'login_failed', { error: params?.error ?? t(locale, 'unknown') }));
|
|
1573
1586
|
}
|
|
1574
1587
|
async restorePendingAuthAdd(record) {
|
|
1575
|
-
const state = await listCodexAuthState();
|
|
1588
|
+
const state = await this.listCodexAuthState();
|
|
1576
1589
|
await this.restoreAuthAfterAddFailure(state.authDir, state.authPath, record.previousTargetPath);
|
|
1577
1590
|
}
|
|
1578
1591
|
async restoreAuthAfterAddFailure(authDir, authPath, previousTargetPath) {
|
|
@@ -1849,6 +1862,9 @@ export class BridgeSessionCore {
|
|
|
1849
1862
|
}
|
|
1850
1863
|
async restorePendingUserInputs() {
|
|
1851
1864
|
for (const stored of this.store.listPendingUserInputs()) {
|
|
1865
|
+
if (!this.ownsScope(stored.chatId)) {
|
|
1866
|
+
continue;
|
|
1867
|
+
}
|
|
1852
1868
|
const record = parseStoredPendingUserInput(stored);
|
|
1853
1869
|
if (!record) {
|
|
1854
1870
|
this.store.markPendingUserInputResolved(stored.localId);
|
|
@@ -2535,6 +2551,34 @@ export class BridgeSessionCore {
|
|
|
2535
2551
|
resolver,
|
|
2536
2552
|
};
|
|
2537
2553
|
}
|
|
2554
|
+
isIdleForServiceUpdate() {
|
|
2555
|
+
return this.activeTurns.size === 0
|
|
2556
|
+
&& this.pendingApprovalMessages.size === 0
|
|
2557
|
+
&& this.pendingUserInputs.size === 0
|
|
2558
|
+
&& this.pendingMcpElicitations.size === 0
|
|
2559
|
+
&& this.pendingLoginsByScope.size === 0
|
|
2560
|
+
&& !this.authRotationInProgress;
|
|
2561
|
+
}
|
|
2562
|
+
hasLocalBlockingActivity() {
|
|
2563
|
+
return !this.isIdleForServiceUpdate();
|
|
2564
|
+
}
|
|
2565
|
+
authRuntimeId() {
|
|
2566
|
+
return this.config.tgScopeBotId ?? 'default';
|
|
2567
|
+
}
|
|
2568
|
+
ownsScope(scopeId) {
|
|
2569
|
+
if (scopeId.startsWith(BRIDGE_SCOPE_WEIXIN_PREFIX)) {
|
|
2570
|
+
return this.messaging.hasWeixinTransport;
|
|
2571
|
+
}
|
|
2572
|
+
if (!this.config.tgScopeBotId) {
|
|
2573
|
+
return parseTelegramTargetFromBridgeScope(scopeId).botId === null;
|
|
2574
|
+
}
|
|
2575
|
+
try {
|
|
2576
|
+
return parseTelegramTargetFromBridgeScope(scopeId).botId === this.config.tgScopeBotId;
|
|
2577
|
+
}
|
|
2578
|
+
catch {
|
|
2579
|
+
return false;
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2538
2582
|
setActiveTurn(scopeId, turnId, active) {
|
|
2539
2583
|
const key = activeTurnKey(scopeId, turnId);
|
|
2540
2584
|
this.activeTurns.set(key, active);
|
|
@@ -2785,7 +2829,7 @@ export class BridgeSessionCore {
|
|
|
2785
2829
|
}
|
|
2786
2830
|
}
|
|
2787
2831
|
for (const scopeId of this.store.findAllChatIdsByThreadId(threadId)) {
|
|
2788
|
-
if (!this.messaging.canSendToScope(scopeId)) {
|
|
2832
|
+
if (!this.ownsScope(scopeId) || !this.messaging.canSendToScope(scopeId)) {
|
|
2789
2833
|
continue;
|
|
2790
2834
|
}
|
|
2791
2835
|
scopes.add(scopeId);
|
|
@@ -2818,7 +2862,12 @@ export class BridgeSessionCore {
|
|
|
2818
2862
|
return next;
|
|
2819
2863
|
}
|
|
2820
2864
|
updateStatus() {
|
|
2821
|
-
|
|
2865
|
+
const status = this.getRuntimeStatus();
|
|
2866
|
+
if (this.coordinator?.statusUpdated) {
|
|
2867
|
+
this.coordinator.statusUpdated(status);
|
|
2868
|
+
return;
|
|
2869
|
+
}
|
|
2870
|
+
writeRuntimeStatus(this.config.statusPath, status);
|
|
2822
2871
|
}
|
|
2823
2872
|
async sendMessage(scopeId, text, inlineKeyboard) {
|
|
2824
2873
|
return this.messaging.sendPlain(scopeId, text, inlineKeyboard);
|
|
@@ -3653,7 +3702,7 @@ export class BridgeSessionCore {
|
|
|
3653
3702
|
].join('\n'));
|
|
3654
3703
|
}
|
|
3655
3704
|
async handleAuthReloadCommand(scopeId, locale) {
|
|
3656
|
-
if (this.
|
|
3705
|
+
if (this.hasLocalBlockingActivity()) {
|
|
3657
3706
|
await this.sendMessage(scopeId, t(locale, 'auth_reload_blocked_active'));
|
|
3658
3707
|
return;
|
|
3659
3708
|
}
|
|
@@ -3670,7 +3719,7 @@ export class BridgeSessionCore {
|
|
|
3670
3719
|
await this.sendMessage(scopeId, t(locale, 'update_unavailable'));
|
|
3671
3720
|
return;
|
|
3672
3721
|
}
|
|
3673
|
-
if (this.
|
|
3722
|
+
if (!this.isIdleForServiceUpdate() || this.store.countPendingApprovals() > 0 || this.store.countPendingUserInputs() > 0 || (this.coordinator?.canSelfUpdate && !this.coordinator.canSelfUpdate())) {
|
|
3674
3723
|
await this.sendMessage(scopeId, t(locale, 'update_blocked_active'));
|
|
3675
3724
|
return;
|
|
3676
3725
|
}
|
|
@@ -3721,17 +3770,23 @@ export class BridgeSessionCore {
|
|
|
3721
3770
|
this.scheduleSelfUpdateStatusPoll();
|
|
3722
3771
|
return;
|
|
3723
3772
|
}
|
|
3773
|
+
if (!this.ownsScope(status.scopeId)) {
|
|
3774
|
+
this.scheduleSelfUpdateStatusPoll();
|
|
3775
|
+
return;
|
|
3776
|
+
}
|
|
3724
3777
|
await this.sendMessage(status.scopeId, this.formatSelfUpdateResult(status));
|
|
3725
3778
|
await this.selfUpdater?.clearStatus();
|
|
3726
3779
|
}
|
|
3727
3780
|
formatSelfUpdateResult(status) {
|
|
3728
3781
|
if (status.state === 'succeeded') {
|
|
3729
|
-
|
|
3782
|
+
const result = t(status.locale, 'update_succeeded', {
|
|
3730
3783
|
from: status.fromVersion,
|
|
3731
3784
|
to: status.toVersion ?? t(status.locale, 'unknown'),
|
|
3732
3785
|
});
|
|
3786
|
+
return status.codexUpdate ? `${result}\n${status.codexUpdate}` : result;
|
|
3733
3787
|
}
|
|
3734
|
-
|
|
3788
|
+
const result = t(status.locale, 'update_failed', { error: status.error ?? t(status.locale, 'unknown') });
|
|
3789
|
+
return status.codexUpdate ? `${result}\n${status.codexUpdate}` : result;
|
|
3735
3790
|
}
|
|
3736
3791
|
async handleAuthCommand(scopeId, locale, args) {
|
|
3737
3792
|
const action = args[0]?.toLowerCase() ?? 'list';
|
|
@@ -3769,7 +3824,7 @@ export class BridgeSessionCore {
|
|
|
3769
3824
|
record.messageId = messageId;
|
|
3770
3825
|
}
|
|
3771
3826
|
async handleAuthUseCommand(scopeId, locale, args) {
|
|
3772
|
-
if (this.
|
|
3827
|
+
if (this.hasLocalBlockingActivity()) {
|
|
3773
3828
|
await this.sendMessage(scopeId, t(locale, 'auth_reload_blocked_active'));
|
|
3774
3829
|
return;
|
|
3775
3830
|
}
|
|
@@ -3807,7 +3862,7 @@ export class BridgeSessionCore {
|
|
|
3807
3862
|
await this.sendMessage(scopeId, renderAuthListMessage(locale, state, parseWeixinBridgeScope(scopeId) !== null));
|
|
3808
3863
|
return;
|
|
3809
3864
|
}
|
|
3810
|
-
this.store.setCodexAuthCandidateDisabled(candidate.name, disabled);
|
|
3865
|
+
this.store.setCodexAuthCandidateDisabled(candidate.name, disabled, this.authRuntimeId());
|
|
3811
3866
|
await this.sendMessage(scopeId, t(locale, disabled ? 'auth_candidate_disabled' : 'auth_candidate_enabled', {
|
|
3812
3867
|
value: candidate.name,
|
|
3813
3868
|
}));
|
|
@@ -3825,7 +3880,7 @@ export class BridgeSessionCore {
|
|
|
3825
3880
|
return latest;
|
|
3826
3881
|
}
|
|
3827
3882
|
async handleAuthAddCommand(scopeId, locale, args) {
|
|
3828
|
-
if (this.
|
|
3883
|
+
if (this.hasLocalBlockingActivity()) {
|
|
3829
3884
|
await this.sendMessage(scopeId, t(locale, 'auth_reload_blocked_active'));
|
|
3830
3885
|
return;
|
|
3831
3886
|
}
|
|
@@ -4259,7 +4314,7 @@ export class BridgeSessionCore {
|
|
|
4259
4314
|
return;
|
|
4260
4315
|
}
|
|
4261
4316
|
const disabled = !candidate.disabled;
|
|
4262
|
-
this.store.setCodexAuthCandidateDisabled(candidate.name, disabled);
|
|
4317
|
+
this.store.setCodexAuthCandidateDisabled(candidate.name, disabled, this.authRuntimeId());
|
|
4263
4318
|
const state = await this.listCodexAuthState();
|
|
4264
4319
|
record.candidates = state.candidates;
|
|
4265
4320
|
await this.messaging.answerCallback(event.callbackQueryId, t(locale, disabled ? 'auth_candidate_disabled_short' : 'auth_candidate_enabled_short'));
|
|
@@ -4277,7 +4332,7 @@ export class BridgeSessionCore {
|
|
|
4277
4332
|
await this.messaging.answerCallback(event.callbackQueryId, t(locale, 'auth_choice_mismatch'));
|
|
4278
4333
|
return;
|
|
4279
4334
|
}
|
|
4280
|
-
if (this.
|
|
4335
|
+
if (this.hasLocalBlockingActivity()) {
|
|
4281
4336
|
await this.messaging.answerCallback(event.callbackQueryId, t(locale, 'auth_reload_blocked_active'));
|
|
4282
4337
|
return;
|
|
4283
4338
|
}
|
|
@@ -4299,7 +4354,7 @@ export class BridgeSessionCore {
|
|
|
4299
4354
|
if (!this.pendingAuthRotation || this.authRotationInProgress) {
|
|
4300
4355
|
return false;
|
|
4301
4356
|
}
|
|
4302
|
-
if (this.
|
|
4357
|
+
if (this.hasLocalBlockingActivity()) {
|
|
4303
4358
|
return false;
|
|
4304
4359
|
}
|
|
4305
4360
|
const rotation = this.pendingAuthRotation;
|
|
@@ -4398,13 +4453,19 @@ export class BridgeSessionCore {
|
|
|
4398
4453
|
return candidate ? { candidate, fromLabel: state.currentLabel, toLabel: await authPathDisplayLabel(candidate.path) } : null;
|
|
4399
4454
|
}
|
|
4400
4455
|
async listCodexAuthState() {
|
|
4401
|
-
const state = await listCodexAuthState(this.store.listDisabledCodexAuthCandidateNames());
|
|
4456
|
+
const state = await listCodexAuthState(this.store.listDisabledCodexAuthCandidateNames(this.authRuntimeId()), this.resolveAuthDir());
|
|
4402
4457
|
const snapshots = await this.readCodexAuthQuotaSnapshots();
|
|
4403
4458
|
state.candidates.forEach((candidate) => {
|
|
4404
4459
|
candidate.quota = snapshots[candidate.name] ?? null;
|
|
4405
4460
|
});
|
|
4406
4461
|
return state;
|
|
4407
4462
|
}
|
|
4463
|
+
resolveAuthDir() {
|
|
4464
|
+
return this.config.codexAuthDir
|
|
4465
|
+
?? (this.config.tgMultiBotMode ? this.config.codexHome : null)
|
|
4466
|
+
?? process.env.CODEX_AUTH_DIR
|
|
4467
|
+
?? path.join(os.homedir(), '.codex');
|
|
4468
|
+
}
|
|
4408
4469
|
async readCodexAuthSwitchLabels(candidate) {
|
|
4409
4470
|
const state = await this.listCodexAuthState();
|
|
4410
4471
|
return {
|
|
@@ -4419,7 +4480,7 @@ export class BridgeSessionCore {
|
|
|
4419
4480
|
};
|
|
4420
4481
|
}
|
|
4421
4482
|
async switchCodexAuthAndRestart(scopeId, locale, candidate, automatic) {
|
|
4422
|
-
const result = await switchCodexAuth(candidate.path);
|
|
4483
|
+
const result = await switchCodexAuth(candidate.path, this.resolveAuthDir());
|
|
4423
4484
|
this.authRotationFailedTargets.delete(candidate.path);
|
|
4424
4485
|
this.pendingTurnErrors.clear();
|
|
4425
4486
|
this.attachedThreads.clear();
|
|
@@ -4544,11 +4605,12 @@ export class BridgeSessionCore {
|
|
|
4544
4605
|
t(locale, 'status_codex_local_tokens', {
|
|
4545
4606
|
total: formatTokenCount(stats.totals.totalTokens),
|
|
4546
4607
|
input: formatTokenCount(stats.totals.inputTokens),
|
|
4608
|
+
visible: formatTokenCount(Math.max(0, stats.totals.outputTokens - stats.totals.reasoningOutputTokens)),
|
|
4547
4609
|
output: formatTokenCount(stats.totals.outputTokens),
|
|
4548
4610
|
cached: formatTokenCount(stats.totals.cachedInputTokens),
|
|
4549
4611
|
reasoning: formatTokenCount(stats.totals.reasoningOutputTokens),
|
|
4550
4612
|
}),
|
|
4551
|
-
...this.
|
|
4613
|
+
...this.formatCodexLocalResponseThroughputStatusLines(locale, stats),
|
|
4552
4614
|
t(locale, 'status_codex_local_snapshot_at', {
|
|
4553
4615
|
value: formatLocalTimestamp(snapshot.computedAtMs / 1000),
|
|
4554
4616
|
}),
|
|
@@ -4559,16 +4621,21 @@ export class BridgeSessionCore {
|
|
|
4559
4621
|
return [t(locale, 'status_codex_local_usage_unavailable', { error: formatShortStatusError(error) })];
|
|
4560
4622
|
}
|
|
4561
4623
|
}
|
|
4562
|
-
|
|
4563
|
-
const
|
|
4564
|
-
if (
|
|
4624
|
+
formatCodexLocalResponseThroughputStatusLines(locale, stats) {
|
|
4625
|
+
const throughput = stats.responseThroughput;
|
|
4626
|
+
if (throughput.completedTurns === 0
|
|
4627
|
+
|| throughput.visibleOutputTokens <= 0
|
|
4628
|
+
|| throughput.seconds <= 0
|
|
4629
|
+
|| throughput.recentCompletedTurns === 0
|
|
4630
|
+
|| throughput.recentVisibleOutputTokens <= 0
|
|
4631
|
+
|| throughput.recentSeconds <= 0) {
|
|
4565
4632
|
return [];
|
|
4566
4633
|
}
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4634
|
+
return [t(locale, 'status_codex_local_throughput', {
|
|
4635
|
+
overall: formatCompactNumber(throughput.visibleOutputTokens / throughput.seconds),
|
|
4636
|
+
recent: formatCompactNumber(throughput.recentVisibleOutputTokens / throughput.recentSeconds),
|
|
4637
|
+
recentTurns: formatTokenCount(throughput.recentCompletedTurns),
|
|
4638
|
+
turns: formatTokenCount(throughput.completedTurns),
|
|
4572
4639
|
})];
|
|
4573
4640
|
}
|
|
4574
4641
|
async resolveFastStatusLabel(locale, settings) {
|
|
@@ -4604,14 +4671,14 @@ export class BridgeSessionCore {
|
|
|
4604
4671
|
await this.localUsageRefresh;
|
|
4605
4672
|
}
|
|
4606
4673
|
async refreshCodexLocalUsageStats() {
|
|
4607
|
-
const stats = await readCodexLocalUsageStats();
|
|
4674
|
+
const stats = await readCodexLocalUsageStats(this.config.codexHome ?? undefined);
|
|
4608
4675
|
const snapshot = { computedAtMs: Date.now(), stats };
|
|
4609
4676
|
this.localUsageCache = snapshot;
|
|
4610
4677
|
this.localUsageCacheLoaded = true;
|
|
4611
4678
|
await writeCodexLocalUsageSnapshot(this.codexLocalUsageSnapshotPath(), snapshot);
|
|
4612
4679
|
}
|
|
4613
4680
|
codexLocalUsageSnapshotPath() {
|
|
4614
|
-
return path.join(path.dirname(this.config.statusPath), CODEX_LOCAL_USAGE_SNAPSHOT_FILENAME);
|
|
4681
|
+
return path.join(path.dirname(this.config.statusPath), this.runtimeSnapshotFilename(CODEX_LOCAL_USAGE_SNAPSHOT_FILENAME));
|
|
4615
4682
|
}
|
|
4616
4683
|
async refreshCurrentCodexAuthQuota(state) {
|
|
4617
4684
|
const candidate = state.candidates.find(entry => entry.isCurrent);
|
|
@@ -4663,7 +4730,13 @@ export class BridgeSessionCore {
|
|
|
4663
4730
|
await fs.rename(temporaryPath, snapshotPath);
|
|
4664
4731
|
}
|
|
4665
4732
|
codexAuthQuotaSnapshotPath() {
|
|
4666
|
-
return path.join(path.dirname(this.config.statusPath), CODEX_AUTH_QUOTA_SNAPSHOT_FILENAME);
|
|
4733
|
+
return path.join(path.dirname(this.config.statusPath), this.runtimeSnapshotFilename(CODEX_AUTH_QUOTA_SNAPSHOT_FILENAME));
|
|
4734
|
+
}
|
|
4735
|
+
runtimeSnapshotFilename(filename) {
|
|
4736
|
+
if (!this.config.tgScopeBotId) {
|
|
4737
|
+
return filename;
|
|
4738
|
+
}
|
|
4739
|
+
return filename.replace(/\.json$/, `-${this.config.tgScopeBotId}.json`);
|
|
4667
4740
|
}
|
|
4668
4741
|
async sendThreadContextSummary(scopeId, locale, threadId) {
|
|
4669
4742
|
try {
|
|
@@ -5582,6 +5655,9 @@ export class BridgeSessionCore {
|
|
|
5582
5655
|
}
|
|
5583
5656
|
async cleanupStaleTurnPreviews() {
|
|
5584
5657
|
for (const preview of this.store.listActiveTurnPreviews()) {
|
|
5658
|
+
if (!this.ownsScope(preview.scopeId)) {
|
|
5659
|
+
continue;
|
|
5660
|
+
}
|
|
5585
5661
|
if (!this.messaging.canSendToScope(preview.scopeId)) {
|
|
5586
5662
|
this.store.removeActiveTurnPreview(preview.turnId);
|
|
5587
5663
|
this.logger.info('telegram.preview_dropped_disabled_channel', {
|
|
@@ -7113,11 +7189,11 @@ function parseActiveTurnKey(key) {
|
|
|
7113
7189
|
turnId: decodeURIComponent(key.slice(split + 1)),
|
|
7114
7190
|
};
|
|
7115
7191
|
}
|
|
7116
|
-
function codexAuthDir() {
|
|
7117
|
-
return process.env.CODEX_AUTH_DIR || path.join(os.homedir(), '.codex');
|
|
7192
|
+
function codexAuthDir(explicitAuthDir = null) {
|
|
7193
|
+
return explicitAuthDir || process.env.CODEX_AUTH_DIR || path.join(os.homedir(), '.codex');
|
|
7118
7194
|
}
|
|
7119
|
-
async function listCodexAuthState(disabledNames = new Set()) {
|
|
7120
|
-
const authDir = codexAuthDir();
|
|
7195
|
+
async function listCodexAuthState(disabledNames = new Set(), explicitAuthDir = null) {
|
|
7196
|
+
const authDir = codexAuthDir(explicitAuthDir);
|
|
7121
7197
|
const authPath = path.join(authDir, 'auth.json');
|
|
7122
7198
|
const currentTargetPath = await resolveCurrentAuthTarget(authDir, authPath);
|
|
7123
7199
|
const candidates = [];
|
|
@@ -7216,8 +7292,8 @@ async function pointCodexAuthAtTarget(authDir, authPath, targetPath) {
|
|
|
7216
7292
|
throw error;
|
|
7217
7293
|
}
|
|
7218
7294
|
}
|
|
7219
|
-
async function switchCodexAuth(targetPath) {
|
|
7220
|
-
const state = await listCodexAuthState();
|
|
7295
|
+
async function switchCodexAuth(targetPath, explicitAuthDir = null) {
|
|
7296
|
+
const state = await listCodexAuthState(new Set(), explicitAuthDir);
|
|
7221
7297
|
const candidate = state.candidates.find(entry => entry.path === targetPath);
|
|
7222
7298
|
if (!candidate) {
|
|
7223
7299
|
throw new Error(`Auth candidate is no longer available: ${path.basename(targetPath)}`);
|
|
@@ -7,12 +7,15 @@ export interface WeixinBridgeScope {
|
|
|
7
7
|
accountId: string;
|
|
8
8
|
fromUserId: string;
|
|
9
9
|
}
|
|
10
|
+
export interface TelegramBridgeTarget extends TelegramScope {
|
|
11
|
+
botId: string | null;
|
|
12
|
+
}
|
|
10
13
|
export declare function isBridgeScopedKey(key: string): boolean;
|
|
11
14
|
/** Wrap legacy Telegram inner scope (`chat::topic`) for storage and routing. */
|
|
12
|
-
export declare function toTelegramBridgeScopeId(telegramInnerScopeId: string): string;
|
|
15
|
+
export declare function toTelegramBridgeScopeId(telegramInnerScopeId: string, botId?: string | null): string;
|
|
13
16
|
/** Strip `telegram:` prefix; returns `null` if not a Telegram bridge scope. */
|
|
14
17
|
export declare function telegramInnerScopeFromBridge(bridgeScopeId: string): string | null;
|
|
15
|
-
export declare function parseTelegramTargetFromBridgeScope(bridgeScopeId: string):
|
|
18
|
+
export declare function parseTelegramTargetFromBridgeScope(bridgeScopeId: string): TelegramBridgeTarget;
|
|
16
19
|
/** Parse `weixin:<accountId>:<from_user_id>`; returns `null` if not a Weixin scope. */
|
|
17
20
|
export declare function parseWeixinBridgeScope(bridgeScopeId: string): WeixinBridgeScope | null;
|
|
18
21
|
export declare function toWeixinBridgeScopeId(accountId: string, fromUserId: string): string;
|
|
@@ -7,8 +7,8 @@ export function isBridgeScopedKey(key) {
|
|
|
7
7
|
return key.startsWith(BRIDGE_SCOPE_TELEGRAM_PREFIX) || key.startsWith(BRIDGE_SCOPE_WEIXIN_PREFIX);
|
|
8
8
|
}
|
|
9
9
|
/** Wrap legacy Telegram inner scope (`chat::topic`) for storage and routing. */
|
|
10
|
-
export function toTelegramBridgeScopeId(telegramInnerScopeId) {
|
|
11
|
-
return `${BRIDGE_SCOPE_TELEGRAM_PREFIX}${telegramInnerScopeId}`;
|
|
10
|
+
export function toTelegramBridgeScopeId(telegramInnerScopeId, botId = null) {
|
|
11
|
+
return `${BRIDGE_SCOPE_TELEGRAM_PREFIX}${botId ? `${botId}:` : ''}${telegramInnerScopeId}`;
|
|
12
12
|
}
|
|
13
13
|
/** Strip `telegram:` prefix; returns `null` if not a Telegram bridge scope. */
|
|
14
14
|
export function telegramInnerScopeFromBridge(bridgeScopeId) {
|
|
@@ -22,7 +22,11 @@ export function parseTelegramTargetFromBridgeScope(bridgeScopeId) {
|
|
|
22
22
|
if (inner === null) {
|
|
23
23
|
throw new Error(`Expected ${BRIDGE_SCOPE_TELEGRAM_PREFIX} scope, got: ${bridgeScopeId}`);
|
|
24
24
|
}
|
|
25
|
-
|
|
25
|
+
const namespaced = /^(bot\d+):(.*)$/.exec(inner);
|
|
26
|
+
if (!namespaced) {
|
|
27
|
+
return { ...parseTelegramScopeId(inner), botId: null };
|
|
28
|
+
}
|
|
29
|
+
return { ...parseTelegramScopeId(namespaced[2]), botId: namespaced[1] };
|
|
26
30
|
}
|
|
27
31
|
/** Parse `weixin:<accountId>:<from_user_id>`; returns `null` if not a Weixin scope. */
|
|
28
32
|
export function parseWeixinBridgeScope(bridgeScopeId) {
|
package/dist/i18n.d.ts
CHANGED
|
@@ -111,8 +111,8 @@ declare const MESSAGES: {
|
|
|
111
111
|
readonly status_codex_usage_reset: ", resets {value}";
|
|
112
112
|
readonly status_codex_usage_unavailable: "Codex usage: unavailable ({error})";
|
|
113
113
|
readonly status_codex_local_history: "Codex local history: {sessions} sessions, {turns} turns, {events} usage records";
|
|
114
|
-
readonly status_codex_local_tokens: "Codex local tokens: total {total}; input {input}, output {
|
|
115
|
-
readonly
|
|
114
|
+
readonly status_codex_local_tokens: "Codex local tokens: total {total}; input {input}, visible output {visible}, reasoning output {reasoning}, total output {output}, cached input {cached}";
|
|
115
|
+
readonly status_codex_local_throughput: "Codex visible reply throughput (end-to-end, excluding reasoning): overall {overall} token/s, last {recentTurns} completed turns {recent} token/s ({turns} completed turns sampled)";
|
|
116
116
|
readonly status_codex_local_snapshot_at: "Codex local stats snapshot: {value}";
|
|
117
117
|
readonly status_codex_local_usage_refreshing: "Codex local history: building snapshot in background";
|
|
118
118
|
readonly status_codex_local_usage_unavailable: "Codex local history: unavailable ({error})";
|
|
@@ -675,8 +675,8 @@ declare const MESSAGES: {
|
|
|
675
675
|
readonly status_codex_usage_reset: ",重置时间 {value}";
|
|
676
676
|
readonly status_codex_usage_unavailable: "Codex 用量:无法获取({error})";
|
|
677
677
|
readonly status_codex_local_history: "Codex 本地历史:{sessions} 个会话,{turns} 轮,{events} 条用量记录";
|
|
678
|
-
readonly status_codex_local_tokens: "Codex 本地 Token:总计 {total};输入 {input}
|
|
679
|
-
readonly
|
|
678
|
+
readonly status_codex_local_tokens: "Codex 本地 Token:总计 {total};输入 {input},可见输出 {visible},推理输出 {reasoning},总输出 {output},缓存输入 {cached}";
|
|
679
|
+
readonly status_codex_local_throughput: "Codex 可见答复吞吐(端到端,排除推理 token):整体 {overall} token/s,最近 {recentTurns} 个完成轮次 {recent} token/s({turns} 个完成轮次样本)";
|
|
680
680
|
readonly status_codex_local_snapshot_at: "Codex 本地统计快照:{value}";
|
|
681
681
|
readonly status_codex_local_usage_refreshing: "Codex 本地历史:正在后台生成统计快照";
|
|
682
682
|
readonly status_codex_local_usage_unavailable: "Codex 本地历史:无法获取({error})";
|
package/dist/i18n.js
CHANGED
|
@@ -109,8 +109,8 @@ const MESSAGES = {
|
|
|
109
109
|
status_codex_usage_reset: ', resets {value}',
|
|
110
110
|
status_codex_usage_unavailable: 'Codex usage: unavailable ({error})',
|
|
111
111
|
status_codex_local_history: 'Codex local history: {sessions} sessions, {turns} turns, {events} usage records',
|
|
112
|
-
status_codex_local_tokens: 'Codex local tokens: total {total}; input {input}, output {
|
|
113
|
-
|
|
112
|
+
status_codex_local_tokens: 'Codex local tokens: total {total}; input {input}, visible output {visible}, reasoning output {reasoning}, total output {output}, cached input {cached}',
|
|
113
|
+
status_codex_local_throughput: 'Codex visible reply throughput (end-to-end, excluding reasoning): overall {overall} token/s, last {recentTurns} completed turns {recent} token/s ({turns} completed turns sampled)',
|
|
114
114
|
status_codex_local_snapshot_at: 'Codex local stats snapshot: {value}',
|
|
115
115
|
status_codex_local_usage_refreshing: 'Codex local history: building snapshot in background',
|
|
116
116
|
status_codex_local_usage_unavailable: 'Codex local history: unavailable ({error})',
|
|
@@ -673,8 +673,8 @@ const MESSAGES = {
|
|
|
673
673
|
status_codex_usage_reset: ',重置时间 {value}',
|
|
674
674
|
status_codex_usage_unavailable: 'Codex 用量:无法获取({error})',
|
|
675
675
|
status_codex_local_history: 'Codex 本地历史:{sessions} 个会话,{turns} 轮,{events} 条用量记录',
|
|
676
|
-
status_codex_local_tokens: 'Codex 本地 Token:总计 {total};输入 {input}
|
|
677
|
-
|
|
676
|
+
status_codex_local_tokens: 'Codex 本地 Token:总计 {total};输入 {input},可见输出 {visible},推理输出 {reasoning},总输出 {output},缓存输入 {cached}',
|
|
677
|
+
status_codex_local_throughput: 'Codex 可见答复吞吐(端到端,排除推理 token):整体 {overall} token/s,最近 {recentTurns} 个完成轮次 {recent} token/s({turns} 个完成轮次样本)',
|
|
678
678
|
status_codex_local_snapshot_at: 'Codex 本地统计快照:{value}',
|
|
679
679
|
status_codex_local_usage_refreshing: 'Codex 本地历史:正在后台生成统计快照',
|
|
680
680
|
status_codex_local_usage_unavailable: 'Codex 本地历史:无法获取({error})',
|