@foxden-app/foxclaw 0.5.77 → 0.6.3
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 +11 -0
- package/CHANGELOG.md +52 -0
- package/README.md +17 -1
- package/README_EN.md +17 -1
- package/dist/auth/cross_node_sync.d.ts +7 -0
- package/dist/auth/cross_node_sync.js +191 -1
- package/dist/codex_app/client.d.ts +1 -0
- package/dist/codex_app/client.js +63 -12
- package/dist/config.d.ts +8 -0
- package/dist/config.js +18 -0
- package/dist/controller/controller.js +25 -23
- package/dist/controller/presentation.js +3 -1
- package/dist/i18n.d.ts +12 -6
- package/dist/i18n.js +45 -6
- package/dist/main.js +20 -1
- package/dist/opencode/client.d.ts +66 -0
- package/dist/opencode/client.js +499 -0
- package/dist/opencode/controller.d.ts +137 -0
- package/dist/opencode/controller.js +2009 -0
- package/dist/opencode/events.d.ts +61 -0
- package/dist/opencode/events.js +145 -0
- package/dist/opencode/runtime.d.ts +14 -0
- package/dist/opencode/runtime.js +29 -0
- package/dist/store/database.d.ts +1 -0
- package/dist/store/database.js +9 -0
- package/dist/telegram/gateway.d.ts +6 -1
- package/dist/telegram/gateway.js +6 -4
- package/dist/types.d.ts +1 -1
- package/docs/cross-node-auth-sync.md +2 -2
- package/docs/user-manual.md +5 -5
- package/docs/zh/cross-node-auth-sync.md +2 -2
- package/docs/zh/user-manual.md +5 -5
- package/package.json +3 -2
|
@@ -523,7 +523,11 @@ export class BridgeSessionCore {
|
|
|
523
523
|
}
|
|
524
524
|
lines.push(...codexUsageLines);
|
|
525
525
|
lines.push(...codexLocalUsageLines);
|
|
526
|
-
|
|
526
|
+
lines.splice(5, 0, t(locale, 'status_codex_home', {
|
|
527
|
+
value: this.config.codexHome ?? path.join(os.homedir(), '.codex'),
|
|
528
|
+
}));
|
|
529
|
+
const messageId = await this.sendRichInternalMessage(scopeId, '/status', lines.join('\n'));
|
|
530
|
+
this.scheduleStalePanelDeletion(scopeId, messageId);
|
|
527
531
|
return;
|
|
528
532
|
}
|
|
529
533
|
case 'account': {
|
|
@@ -4948,7 +4952,7 @@ export class BridgeSessionCore {
|
|
|
4948
4952
|
await this.sendMessage(scopeId, message);
|
|
4949
4953
|
return;
|
|
4950
4954
|
}
|
|
4951
|
-
if (action === 'audit' || action === 'check') {
|
|
4955
|
+
if (action === 'audit' || action === 'check' || action === 'safe') {
|
|
4952
4956
|
if (!this.canRunGlobalAuthRefresh()) {
|
|
4953
4957
|
await this.sendMessage(scopeId, t(locale, 'auth_cluster_audit_blocked_active'));
|
|
4954
4958
|
return;
|
|
@@ -4959,10 +4963,10 @@ export class BridgeSessionCore {
|
|
|
4959
4963
|
await this.sendMessage(scopeId, t(locale, 'auth_sync_disabled'));
|
|
4960
4964
|
return;
|
|
4961
4965
|
}
|
|
4962
|
-
await this.sendRichInternalMessage(scopeId, '/auth sync
|
|
4966
|
+
await this.sendRichInternalMessage(scopeId, '/auth sync safe', formatAuthClusterAuditResult(locale, outcome));
|
|
4963
4967
|
return;
|
|
4964
4968
|
}
|
|
4965
|
-
if (action === '
|
|
4969
|
+
if (action === 'push' && args[1]?.toLowerCase() === 'all') {
|
|
4966
4970
|
if (!this.canRunGlobalAuthRefresh()) {
|
|
4967
4971
|
await this.sendMessage(scopeId, t(locale, 'auth_sync_push_blocked_active'));
|
|
4968
4972
|
return;
|
|
@@ -5803,8 +5807,20 @@ export class BridgeSessionCore {
|
|
|
5803
5807
|
if (record.messageId !== null) {
|
|
5804
5808
|
await this.editAuthPanelMessage(event.scopeId, record.messageId, t(locale, 'auth_sync_safe_starting'), []);
|
|
5805
5809
|
}
|
|
5806
|
-
|
|
5807
|
-
|
|
5810
|
+
if (record.messageId !== null) {
|
|
5811
|
+
this.pauseStalePanelDeletion(event.scopeId, record.messageId);
|
|
5812
|
+
}
|
|
5813
|
+
let outcome;
|
|
5814
|
+
try {
|
|
5815
|
+
outcome = await this.runAuthClusterAudit();
|
|
5816
|
+
}
|
|
5817
|
+
catch (error) {
|
|
5818
|
+
if (record.messageId !== null) {
|
|
5819
|
+
await this.editAuthPanelMessage(event.scopeId, record.messageId, t(locale, 'auth_cluster_audit_failed', { error: formatUserError(error) }), authChoiceKeyboard(locale, record));
|
|
5820
|
+
}
|
|
5821
|
+
return;
|
|
5822
|
+
}
|
|
5823
|
+
if (!outcome) {
|
|
5808
5824
|
if (record.messageId !== null) {
|
|
5809
5825
|
await this.editAuthPanelMessage(event.scopeId, record.messageId, t(locale, 'auth_sync_disabled'), authChoiceKeyboard(locale, record));
|
|
5810
5826
|
}
|
|
@@ -5816,12 +5832,7 @@ export class BridgeSessionCore {
|
|
|
5816
5832
|
record.createdAt = Date.now();
|
|
5817
5833
|
clampCodexAuthListOffset(record);
|
|
5818
5834
|
if (record.messageId !== null) {
|
|
5819
|
-
await this.editAuthPanelMessage(event.scopeId, record.messageId, `${
|
|
5820
|
-
localSynced: result.localSynced,
|
|
5821
|
-
localSkipped: result.localSkipped,
|
|
5822
|
-
sent: result.sent,
|
|
5823
|
-
skipped: result.skipped,
|
|
5824
|
-
})}\n\n${renderAuthListMessage(locale, state, this.authDisplayBotLabel(), parseWeixinBridgeScope(event.scopeId) !== null, record)}`, authChoiceKeyboard(locale, record));
|
|
5835
|
+
await this.editAuthPanelMessage(event.scopeId, record.messageId, `${formatAuthClusterAuditResult(locale, outcome)}\n\n${renderAuthListMessage(locale, state, this.authDisplayBotLabel(), parseWeixinBridgeScope(event.scopeId) !== null, record)}`, authChoiceKeyboard(locale, record));
|
|
5825
5836
|
}
|
|
5826
5837
|
return;
|
|
5827
5838
|
}
|
|
@@ -10493,17 +10504,8 @@ function authChoiceKeyboard(locale, record) {
|
|
|
10493
10504
|
if (record.searchTerm) {
|
|
10494
10505
|
rows.push([{ text: t(locale, 'button_clear_filter'), callback_data: `auth:${record.localId}:clear_search` }]);
|
|
10495
10506
|
}
|
|
10496
|
-
rows.push([
|
|
10497
|
-
|
|
10498
|
-
{ text: t(locale, 'button_login_device'), callback_data: `auth:${record.localId}:login_device` },
|
|
10499
|
-
]);
|
|
10500
|
-
rows.push([
|
|
10501
|
-
{ text: t(locale, 'button_auth_cluster_audit'), callback_data: `auth:${record.localId}:cluster_audit` },
|
|
10502
|
-
]);
|
|
10503
|
-
rows.push([
|
|
10504
|
-
{ text: t(locale, 'button_auth_safe_sync'), callback_data: `auth:${record.localId}:safe_sync` },
|
|
10505
|
-
{ text: t(locale, 'button_auth_reload'), callback_data: `auth:${record.localId}:reload` },
|
|
10506
|
-
]);
|
|
10507
|
+
rows.push([{ text: t(locale, 'button_login_device'), callback_data: `auth:${record.localId}:login_device` }]);
|
|
10508
|
+
rows.push([{ text: t(locale, 'button_auth_safe_sync'), callback_data: `auth:${record.localId}:safe_sync` }]);
|
|
10507
10509
|
return rows;
|
|
10508
10510
|
}
|
|
10509
10511
|
function formatCodexAuthCandidateDisplayName(name) {
|
|
@@ -542,7 +542,9 @@ export function normalizeRequestedEffort(value) {
|
|
|
542
542
|
|| normalized === 'low'
|
|
543
543
|
|| normalized === 'medium'
|
|
544
544
|
|| normalized === 'high'
|
|
545
|
-
|| normalized === 'xhigh'
|
|
545
|
+
|| normalized === 'xhigh'
|
|
546
|
+
|| normalized === 'max'
|
|
547
|
+
|| normalized === 'ultra') {
|
|
546
548
|
return normalized;
|
|
547
549
|
}
|
|
548
550
|
return null;
|
package/dist/i18n.d.ts
CHANGED
|
@@ -95,6 +95,7 @@ declare const MESSAGES: {
|
|
|
95
95
|
readonly status_user_agent: "Codex/FoxClaw agent: {value}";
|
|
96
96
|
readonly status_current_thread: "Current thread: {value}";
|
|
97
97
|
readonly status_configured_model: "Configured model: {value}";
|
|
98
|
+
readonly status_codex_home: "Codex home: {value}";
|
|
98
99
|
readonly status_configured_effort: "Configured effort: {value}";
|
|
99
100
|
readonly status_fast: "Fast: {value}";
|
|
100
101
|
readonly status_collaboration_mode: "Mode: {value}";
|
|
@@ -245,7 +246,7 @@ declare const MESSAGES: {
|
|
|
245
246
|
readonly auth_sync_test_sent: "Auth sync test complete: sent {sent}, replies {replied}.";
|
|
246
247
|
readonly auth_sync_test_missing: "Missing replies: {value}";
|
|
247
248
|
readonly auth_sync_push_blocked_active: "Cannot push auth sync while any runtime, approval, input, login, or auth mirror write is active.";
|
|
248
|
-
readonly auth_sync_safe_starting: "
|
|
249
|
+
readonly auth_sync_safe_starting: "Auditing every node, selecting the newest valid auth, and safely synchronizing the cluster...";
|
|
249
250
|
readonly auth_sync_safe_done: "Safe auth sync complete: local synced {localSynced}, local skipped {localSkipped}; cross-node sent {sent}, skipped {skipped}.";
|
|
250
251
|
readonly auth_cluster_audit_starting: "Asking all auth-sync peers to validate accounts, reconcile the newest usable credentials, and refresh credentials at least 8 days old...";
|
|
251
252
|
readonly auth_cluster_audit_blocked_active: "Cluster auth check requires every local runtime, approval, input, login, and auth mirror write to be idle.";
|
|
@@ -282,7 +283,7 @@ declare const MESSAGES: {
|
|
|
282
283
|
readonly auth_sync_trace_missing: "Usage: /auth sync trace <requestId>";
|
|
283
284
|
readonly button_login_device: "🔑 Login";
|
|
284
285
|
readonly button_auth_reload: "🔄 Reload auth";
|
|
285
|
-
readonly button_auth_safe_sync: "
|
|
286
|
+
readonly button_auth_safe_sync: "🩺 Safe sync";
|
|
286
287
|
readonly button_auth_cluster_audit: "🩺 Check all nodes and reconcile auth";
|
|
287
288
|
readonly button_auth_refresh_all_confirm: "⚠️ Accept risk & refresh";
|
|
288
289
|
readonly button_auth_repair_login: "🔑 Login repair";
|
|
@@ -394,7 +395,7 @@ declare const MESSAGES: {
|
|
|
394
395
|
readonly effort_adjusted_model: "Effort {effort} is not supported by {model}, so it was adjusted.";
|
|
395
396
|
readonly effort_change_blocked: "Cannot change reasoning effort while a turn is active. Use /interrupt or wait.";
|
|
396
397
|
readonly effort_reset: "Configured effort reset to server default.";
|
|
397
|
-
readonly usage_effort: "Usage: /effort <none|minimal|low|medium|high|xhigh|default>\nOr just use /models and tap buttons.";
|
|
398
|
+
readonly usage_effort: "Usage: /effort <none|minimal|low|medium|high|xhigh|max|ultra|default>\nOr just use /models and tap buttons.";
|
|
398
399
|
readonly model_does_not_support_effort: "{model} does not support {effort}.\nSupported: {supported}";
|
|
399
400
|
readonly effort_configured: "Configured effort: {effort}";
|
|
400
401
|
readonly usage_fast: "Usage: /fast <on|off|toggle>";
|
|
@@ -818,6 +819,7 @@ declare const MESSAGES: {
|
|
|
818
819
|
readonly status_user_agent: "Codex/FoxClaw 标识:{value}";
|
|
819
820
|
readonly status_current_thread: "当前线程:{value}";
|
|
820
821
|
readonly status_configured_model: "已配置模型:{value}";
|
|
822
|
+
readonly status_codex_home: "Codex Home:{value}";
|
|
821
823
|
readonly status_configured_effort: "已配置推理强度:{value}";
|
|
822
824
|
readonly status_fast: "Fast:{value}";
|
|
823
825
|
readonly status_collaboration_mode: "模式:{value}";
|
|
@@ -968,7 +970,7 @@ declare const MESSAGES: {
|
|
|
968
970
|
readonly auth_sync_test_sent: "auth sync 测试完成:已发送 {sent},收到回应 {replied}。";
|
|
969
971
|
readonly auth_sync_test_missing: "未回应:{value}";
|
|
970
972
|
readonly auth_sync_push_blocked_active: "当前有任一 runtime、审批、待输入、登录或 auth 镜像写入在进行中,不能推送 auth 同步。";
|
|
971
|
-
readonly auth_sync_safe_starting: "
|
|
973
|
+
readonly auth_sync_safe_starting: "正在自检全部节点、选择最新有效 auth,并安全同步整个集群...";
|
|
972
974
|
readonly auth_sync_safe_done: "安全 auth 同步完成:本机同步 {localSynced},本机跳过 {localSkipped};跨节点发送 {sent},跳过 {skipped}。";
|
|
973
975
|
readonly auth_cluster_audit_starting: "正在通知所有 auth sync 节点逐账号自检,协商最新有效凭据,并由本节点刷新已满 8 天的 auth...";
|
|
974
976
|
readonly auth_cluster_audit_blocked_active: "集群 auth 自检要求本机所有 runtime、审批、待输入、登录和 auth 镜像写入均为空闲。";
|
|
@@ -1005,7 +1007,7 @@ declare const MESSAGES: {
|
|
|
1005
1007
|
readonly auth_sync_trace_missing: "用法:/auth sync trace <requestId>";
|
|
1006
1008
|
readonly button_login_device: "🔑 设备登录";
|
|
1007
1009
|
readonly button_auth_reload: "🔄 重载 auth";
|
|
1008
|
-
readonly button_auth_safe_sync: "
|
|
1010
|
+
readonly button_auth_safe_sync: "🩺 安全同步";
|
|
1009
1011
|
readonly button_auth_cluster_audit: "🩺 全节点自检并同步";
|
|
1010
1012
|
readonly button_auth_refresh_all_confirm: "⚠️ 接受风险并刷新";
|
|
1011
1013
|
readonly button_auth_repair_login: "🔑 登录修复";
|
|
@@ -1117,7 +1119,7 @@ declare const MESSAGES: {
|
|
|
1117
1119
|
readonly effort_adjusted_model: "{model} 不支持 {effort},所以已自动调整推理强度。";
|
|
1118
1120
|
readonly effort_change_blocked: "当前有回复在进行中,暂时不能切换推理强度。请先等待,或使用 /interrupt。";
|
|
1119
1121
|
readonly effort_reset: "推理强度已重置为服务端默认值。";
|
|
1120
|
-
readonly usage_effort: "用法:/effort <none|minimal|low|medium|high|xhigh|default>\n或者直接使用 /models 点按钮。";
|
|
1122
|
+
readonly usage_effort: "用法:/effort <none|minimal|low|medium|high|xhigh|max|ultra|default>\n或者直接使用 /models 点按钮。";
|
|
1121
1123
|
readonly model_does_not_support_effort: "{model} 不支持 {effort}。\n支持的强度:{supported}";
|
|
1122
1124
|
readonly effort_configured: "已配置推理强度:{effort}";
|
|
1123
1125
|
readonly usage_fast: "用法:/fast <on|off|toggle>";
|
|
@@ -1455,4 +1457,8 @@ export declare function getTelegramCommands(locale: AppLocale): Array<{
|
|
|
1455
1457
|
command: string;
|
|
1456
1458
|
description: string;
|
|
1457
1459
|
}>;
|
|
1460
|
+
export declare function getOpencodeTelegramCommands(locale: AppLocale): Array<{
|
|
1461
|
+
command: string;
|
|
1462
|
+
description: string;
|
|
1463
|
+
}>;
|
|
1458
1464
|
export {};
|
package/dist/i18n.js
CHANGED
|
@@ -93,6 +93,7 @@ const MESSAGES = {
|
|
|
93
93
|
status_user_agent: 'Codex/FoxClaw agent: {value}',
|
|
94
94
|
status_current_thread: 'Current thread: {value}',
|
|
95
95
|
status_configured_model: 'Configured model: {value}',
|
|
96
|
+
status_codex_home: 'Codex home: {value}',
|
|
96
97
|
status_configured_effort: 'Configured effort: {value}',
|
|
97
98
|
status_fast: 'Fast: {value}',
|
|
98
99
|
status_collaboration_mode: 'Mode: {value}',
|
|
@@ -243,7 +244,7 @@ const MESSAGES = {
|
|
|
243
244
|
auth_sync_test_sent: 'Auth sync test complete: sent {sent}, replies {replied}.',
|
|
244
245
|
auth_sync_test_missing: 'Missing replies: {value}',
|
|
245
246
|
auth_sync_push_blocked_active: 'Cannot push auth sync while any runtime, approval, input, login, or auth mirror write is active.',
|
|
246
|
-
auth_sync_safe_starting: '
|
|
247
|
+
auth_sync_safe_starting: 'Auditing every node, selecting the newest valid auth, and safely synchronizing the cluster...',
|
|
247
248
|
auth_sync_safe_done: 'Safe auth sync complete: local synced {localSynced}, local skipped {localSkipped}; cross-node sent {sent}, skipped {skipped}.',
|
|
248
249
|
auth_cluster_audit_starting: 'Asking all auth-sync peers to validate accounts, reconcile the newest usable credentials, and refresh credentials at least 8 days old...',
|
|
249
250
|
auth_cluster_audit_blocked_active: 'Cluster auth check requires every local runtime, approval, input, login, and auth mirror write to be idle.',
|
|
@@ -280,7 +281,7 @@ const MESSAGES = {
|
|
|
280
281
|
auth_sync_trace_missing: 'Usage: /auth sync trace <requestId>',
|
|
281
282
|
button_login_device: '🔑 Login',
|
|
282
283
|
button_auth_reload: '🔄 Reload auth',
|
|
283
|
-
button_auth_safe_sync: '
|
|
284
|
+
button_auth_safe_sync: '🩺 Safe sync',
|
|
284
285
|
button_auth_cluster_audit: '🩺 Check all nodes and reconcile auth',
|
|
285
286
|
button_auth_refresh_all_confirm: '⚠️ Accept risk & refresh',
|
|
286
287
|
button_auth_repair_login: '🔑 Login repair',
|
|
@@ -392,7 +393,7 @@ const MESSAGES = {
|
|
|
392
393
|
effort_adjusted_model: 'Effort {effort} is not supported by {model}, so it was adjusted.',
|
|
393
394
|
effort_change_blocked: 'Cannot change reasoning effort while a turn is active. Use /interrupt or wait.',
|
|
394
395
|
effort_reset: 'Configured effort reset to server default.',
|
|
395
|
-
usage_effort: 'Usage: /effort <none|minimal|low|medium|high|xhigh|default>\nOr just use /models and tap buttons.',
|
|
396
|
+
usage_effort: 'Usage: /effort <none|minimal|low|medium|high|xhigh|max|ultra|default>\nOr just use /models and tap buttons.',
|
|
396
397
|
model_does_not_support_effort: '{model} does not support {effort}.\nSupported: {supported}',
|
|
397
398
|
effort_configured: 'Configured effort: {effort}',
|
|
398
399
|
usage_fast: 'Usage: /fast <on|off|toggle>',
|
|
@@ -816,6 +817,7 @@ const MESSAGES = {
|
|
|
816
817
|
status_user_agent: 'Codex/FoxClaw 标识:{value}',
|
|
817
818
|
status_current_thread: '当前线程:{value}',
|
|
818
819
|
status_configured_model: '已配置模型:{value}',
|
|
820
|
+
status_codex_home: 'Codex Home:{value}',
|
|
819
821
|
status_configured_effort: '已配置推理强度:{value}',
|
|
820
822
|
status_fast: 'Fast:{value}',
|
|
821
823
|
status_collaboration_mode: '模式:{value}',
|
|
@@ -966,7 +968,7 @@ const MESSAGES = {
|
|
|
966
968
|
auth_sync_test_sent: 'auth sync 测试完成:已发送 {sent},收到回应 {replied}。',
|
|
967
969
|
auth_sync_test_missing: '未回应:{value}',
|
|
968
970
|
auth_sync_push_blocked_active: '当前有任一 runtime、审批、待输入、登录或 auth 镜像写入在进行中,不能推送 auth 同步。',
|
|
969
|
-
auth_sync_safe_starting: '
|
|
971
|
+
auth_sync_safe_starting: '正在自检全部节点、选择最新有效 auth,并安全同步整个集群...',
|
|
970
972
|
auth_sync_safe_done: '安全 auth 同步完成:本机同步 {localSynced},本机跳过 {localSkipped};跨节点发送 {sent},跳过 {skipped}。',
|
|
971
973
|
auth_cluster_audit_starting: '正在通知所有 auth sync 节点逐账号自检,协商最新有效凭据,并由本节点刷新已满 8 天的 auth...',
|
|
972
974
|
auth_cluster_audit_blocked_active: '集群 auth 自检要求本机所有 runtime、审批、待输入、登录和 auth 镜像写入均为空闲。',
|
|
@@ -1003,7 +1005,7 @@ const MESSAGES = {
|
|
|
1003
1005
|
auth_sync_trace_missing: '用法:/auth sync trace <requestId>',
|
|
1004
1006
|
button_login_device: '🔑 设备登录',
|
|
1005
1007
|
button_auth_reload: '🔄 重载 auth',
|
|
1006
|
-
button_auth_safe_sync: '
|
|
1008
|
+
button_auth_safe_sync: '🩺 安全同步',
|
|
1007
1009
|
button_auth_cluster_audit: '🩺 全节点自检并同步',
|
|
1008
1010
|
button_auth_refresh_all_confirm: '⚠️ 接受风险并刷新',
|
|
1009
1011
|
button_auth_repair_login: '🔑 登录修复',
|
|
@@ -1115,7 +1117,7 @@ const MESSAGES = {
|
|
|
1115
1117
|
effort_adjusted_model: '{model} 不支持 {effort},所以已自动调整推理强度。',
|
|
1116
1118
|
effort_change_blocked: '当前有回复在进行中,暂时不能切换推理强度。请先等待,或使用 /interrupt。',
|
|
1117
1119
|
effort_reset: '推理强度已重置为服务端默认值。',
|
|
1118
|
-
usage_effort: '用法:/effort <none|minimal|low|medium|high|xhigh|default>\n或者直接使用 /models 点按钮。',
|
|
1120
|
+
usage_effort: '用法:/effort <none|minimal|low|medium|high|xhigh|max|ultra|default>\n或者直接使用 /models 点按钮。',
|
|
1119
1121
|
model_does_not_support_effort: '{model} 不支持 {effort}。\n支持的强度:{supported}',
|
|
1120
1122
|
effort_configured: '已配置推理强度:{effort}',
|
|
1121
1123
|
usage_fast: '用法:/fast <on|off|toggle>',
|
|
@@ -1505,3 +1507,40 @@ export function getTelegramCommands(locale) {
|
|
|
1505
1507
|
{ command: 'interrupt', description: t(locale, 'cmd_desc_interrupt') },
|
|
1506
1508
|
];
|
|
1507
1509
|
}
|
|
1510
|
+
export function getOpencodeTelegramCommands(locale) {
|
|
1511
|
+
const descriptions = locale === 'zh' ? {
|
|
1512
|
+
help: '查看命令', setup: 'OpenCode 设置', status: '桥接状态', update: '请改用 Codex Bot', threads: '最近会话', open: '打开会话',
|
|
1513
|
+
watch: '观察会话', unwatch: '停止观察', new: '新建会话', models: '模型列表', model: '选择模型',
|
|
1514
|
+
effort: '推理档位', fast: 'Fast 能力说明', mode: 'Agent 或 Plan', plan: '下一轮 Plan', agent: '选择 Agent', permissions: '权限设置',
|
|
1515
|
+
active: '运行中新消息', steer: '引导当前回复', queue: '排队下一轮', takeover: '中断并接管',
|
|
1516
|
+
history: '最近消息', files: '查找文件', fork: 'Fork 会话', undo: '原生回退', redo: '原生重做', rename: '重命名会话',
|
|
1517
|
+
review: '原生代码审查', archive: '归档当前会话', unarchive: '恢复归档会话', rich: '富文本测试', voice: '朗读文本或发送语音',
|
|
1518
|
+
diff: '会话文件变更', where: '当前会话信息', reveal: '当前会话信息', compact: '压缩上下文', loaded: '已加载会话', skills: 'OpenCode Skills',
|
|
1519
|
+
mcp: 'MCP 状态', apps: 'MCP 应用', provider: '模型 Provider', auth: 'Provider 认证状态', auth_reload: '无 OpenCode 等价 API',
|
|
1520
|
+
account: '无 OpenCode 账户 API', quota: '无 OpenCode 配额 API', login_device: '请用终端 opencode auth', goal: '无 OpenCode Goal 原语', remote: 'Codex Remote 专用',
|
|
1521
|
+
plugins: 'OpenCode Plugins', hooks: 'Plugin hooks', features: '实验能力', config: '配置摘要', approve: '待审批', answer: '待回答', interrupt: '中断回复',
|
|
1522
|
+
} : {
|
|
1523
|
+
help: 'Show commands', setup: 'OpenCode settings', status: 'Bridge status', update: 'Use the Codex bot', threads: 'Recent sessions', open: 'Open a session',
|
|
1524
|
+
watch: 'Watch a session', unwatch: 'Stop watching', new: 'New session', models: 'Model list', model: 'Select model',
|
|
1525
|
+
effort: 'Reasoning variant', fast: 'Fast capability note', mode: 'Agent or Plan', plan: 'Plan next turn', agent: 'Select agent', permissions: 'Access settings',
|
|
1526
|
+
active: 'Active-turn messages', steer: 'Steer active turn', queue: 'Queue next turn', takeover: 'Interrupt and take over',
|
|
1527
|
+
history: 'Recent messages', files: 'Find files', fork: 'Fork session', undo: 'Native undo', redo: 'Native redo', rename: 'Rename session',
|
|
1528
|
+
review: 'Native code review', archive: 'Archive current session', unarchive: 'Restore archived session', rich: 'Rich-text test', voice: 'Read text or send voice',
|
|
1529
|
+
diff: 'Session file changes', where: 'Current session', reveal: 'Current session', compact: 'Compact context', loaded: 'Loaded sessions', skills: 'OpenCode skills',
|
|
1530
|
+
mcp: 'MCP status', apps: 'MCP apps', provider: 'Model providers', auth: 'Provider auth status', auth_reload: 'No OpenCode equivalent',
|
|
1531
|
+
account: 'No OpenCode account API', quota: 'No OpenCode quota API', login_device: 'Use opencode auth terminal', goal: 'No OpenCode Goal primitive', remote: 'Codex Remote only',
|
|
1532
|
+
plugins: 'OpenCode plugins', hooks: 'Plugin hooks', features: 'Experimental capabilities', config: 'Config summary', approve: 'Pending approvals', answer: 'Pending questions', interrupt: 'Stop active turn',
|
|
1533
|
+
};
|
|
1534
|
+
const localized = descriptions;
|
|
1535
|
+
const codexMenu = getTelegramCommands(locale).map((entry) => ({
|
|
1536
|
+
command: entry.command,
|
|
1537
|
+
description: localized[entry.command] ?? entry.description,
|
|
1538
|
+
}));
|
|
1539
|
+
const shared = new Set(codexMenu.map((entry) => entry.command));
|
|
1540
|
+
return [
|
|
1541
|
+
...codexMenu,
|
|
1542
|
+
...Object.entries(localized)
|
|
1543
|
+
.filter(([command]) => !shared.has(command))
|
|
1544
|
+
.map(([command, description]) => ({ command, description })),
|
|
1545
|
+
];
|
|
1546
|
+
}
|
package/dist/main.js
CHANGED
|
@@ -552,7 +552,7 @@ function truncateStatusLine(value, maxLength) {
|
|
|
552
552
|
return normalized.length <= maxLength ? normalized : `${normalized.slice(0, Math.max(0, maxLength - 1))}…`;
|
|
553
553
|
}
|
|
554
554
|
async function runServeCli() {
|
|
555
|
-
const [{ BridgeMessagingRouter }, { TelegramMessagingPort }, { WeixinChannelAdapter }, { WeixinMessagingPort }, { attachIlinkRuntimeFromBridgeLogger }, { loadWeixinAccount }, { Logger }, { BridgeStore }, { TelegramGateway }, { CodexAppClient }, { BridgeSessionCore }, { TelegramChannelAdapter }, { AuthCandidateMirror }, { CrossNodeAuthSync },] = await Promise.all([
|
|
555
|
+
const [{ BridgeMessagingRouter }, { TelegramMessagingPort }, { WeixinChannelAdapter }, { WeixinMessagingPort }, { attachIlinkRuntimeFromBridgeLogger }, { loadWeixinAccount }, { Logger }, { BridgeStore }, { TelegramGateway }, { CodexAppClient }, { BridgeSessionCore }, { TelegramChannelAdapter }, { AuthCandidateMirror }, { CrossNodeAuthSync }, { OpencodeTelegramRuntime },] = await Promise.all([
|
|
556
556
|
import('./channels/bridge_messaging_router.js'),
|
|
557
557
|
import('./channels/telegram/telegram_messaging_port.js'),
|
|
558
558
|
import('./channels/weixin/weixin_channel_adapter.js'),
|
|
@@ -567,6 +567,7 @@ async function runServeCli() {
|
|
|
567
567
|
import('./channels/telegram/telegram_channel_adapter.js'),
|
|
568
568
|
import('./auth/mirror.js'),
|
|
569
569
|
import('./auth/cross_node_sync.js'),
|
|
570
|
+
import('./opencode/runtime.js'),
|
|
570
571
|
]);
|
|
571
572
|
const config = loadConfig();
|
|
572
573
|
const logger = new Logger(config.logLevel, config.logPath);
|
|
@@ -581,8 +582,14 @@ async function runServeCli() {
|
|
|
581
582
|
let managedApps = [];
|
|
582
583
|
let activeAuthMirror = null;
|
|
583
584
|
let activeAuthSync = null;
|
|
585
|
+
let activeOpencodeRuntime = null;
|
|
584
586
|
try {
|
|
585
587
|
store = new BridgeStore(config.storePath);
|
|
588
|
+
if (config.opencodeBotToken) {
|
|
589
|
+
activeOpencodeRuntime = new OpencodeTelegramRuntime(config, store, logger);
|
|
590
|
+
await activeOpencodeRuntime.start();
|
|
591
|
+
logger.info('opencode.bridge.started', activeOpencodeRuntime.getRuntimeStatus());
|
|
592
|
+
}
|
|
586
593
|
if (config.tgMultiBotMode) {
|
|
587
594
|
const seeds = [];
|
|
588
595
|
const canonicalAuthDir = config.codexAuthDir ?? config.codexHome ?? path.join(os.homedir(), '.codex');
|
|
@@ -881,6 +888,7 @@ async function runServeCli() {
|
|
|
881
888
|
await weixinAdapter?.stop();
|
|
882
889
|
await activeWeixinCore?.stop();
|
|
883
890
|
await Promise.all(runtimes.map((runtime) => runtime.telegram.stop()));
|
|
891
|
+
await activeOpencodeRuntime?.stop();
|
|
884
892
|
writeAggregateStatus(false);
|
|
885
893
|
await Promise.all(managedApps.map((app) => app.stop({ terminateServer: true }).catch((error) => {
|
|
886
894
|
logger.warn('codex.app-server.stop_failed', { error: serializeError(error) });
|
|
@@ -1060,6 +1068,7 @@ async function runServeCli() {
|
|
|
1060
1068
|
singleMirror?.stop();
|
|
1061
1069
|
await weixinAdapter?.stop();
|
|
1062
1070
|
await telegram.stop();
|
|
1071
|
+
await activeOpencodeRuntime?.stop();
|
|
1063
1072
|
writeRuntimeStatus(config.statusPath, {
|
|
1064
1073
|
running: false,
|
|
1065
1074
|
connected: false,
|
|
@@ -1093,6 +1102,7 @@ async function runServeCli() {
|
|
|
1093
1102
|
await weixinAdapter?.stop().catch(() => { });
|
|
1094
1103
|
await activeWeixinCore?.stop().catch(() => { });
|
|
1095
1104
|
await Promise.allSettled(activeTelegramAdapters.map((adapter) => adapter.stop()));
|
|
1105
|
+
await activeOpencodeRuntime?.stop().catch(() => { });
|
|
1096
1106
|
await Promise.allSettled(managedApps.map((app) => app.stop({ terminateServer: true })));
|
|
1097
1107
|
store?.close();
|
|
1098
1108
|
processLock.release();
|
|
@@ -1778,6 +1788,15 @@ function runDoctorChecks() {
|
|
|
1778
1788
|
['telegram bot token(s) configured', Boolean(process.env.TG_BOT_TOKENS?.trim() || process.env.TG_BOT_TOKEN?.trim())],
|
|
1779
1789
|
['telegram allowed user configured', Boolean(process.env.TG_ALLOWED_USER_ID)],
|
|
1780
1790
|
];
|
|
1791
|
+
if (process.env.OPENCODE_BOT_TOKEN?.trim()) {
|
|
1792
|
+
const configuredOpencodeBin = process.env.OPENCODE_CLI_BIN;
|
|
1793
|
+
checks.push(['opencode cli available', hasConfiguredCodexBin(configuredOpencodeBin) || hasCommand('opencode')]);
|
|
1794
|
+
const codexTokens = [
|
|
1795
|
+
...(process.env.TG_BOT_TOKENS ?? '').split(','),
|
|
1796
|
+
process.env.TG_BOT_TOKEN ?? '',
|
|
1797
|
+
].map((value) => value.trim()).filter(Boolean);
|
|
1798
|
+
checks.push(['OpenCode uses an independent Telegram bot', !codexTokens.includes(process.env.OPENCODE_BOT_TOKEN.trim())]);
|
|
1799
|
+
}
|
|
1781
1800
|
if (process.env.WX_ENABLED === 'true' || process.env.WX_ENABLED === '1') {
|
|
1782
1801
|
const accountsDir = process.env.WEIXIN_ACCOUNTS_DIR || path.join(APP_HOME, 'weixin', 'accounts');
|
|
1783
1802
|
let hasAccounts = false;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { type OpencodeClient } from '@opencode-ai/sdk/v2';
|
|
3
|
+
import type { Logger } from '../logger.js';
|
|
4
|
+
import { type OpencodeBridgeEvent } from './events.js';
|
|
5
|
+
export interface OpencodeServerRuntimeStatus {
|
|
6
|
+
pid: number | null;
|
|
7
|
+
port: number | null;
|
|
8
|
+
running: boolean;
|
|
9
|
+
connected: boolean;
|
|
10
|
+
url: string | null;
|
|
11
|
+
version: string | null;
|
|
12
|
+
managed: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface OpencodeAppClientEvents {
|
|
15
|
+
event: [event: OpencodeBridgeEvent];
|
|
16
|
+
connected: [];
|
|
17
|
+
disconnected: [detail: {
|
|
18
|
+
source: string;
|
|
19
|
+
message?: string;
|
|
20
|
+
}];
|
|
21
|
+
}
|
|
22
|
+
/** Owns one password-protected localhost `opencode serve` process. */
|
|
23
|
+
export declare class OpencodeAppClient extends EventEmitter<OpencodeAppClientEvents> {
|
|
24
|
+
private readonly cliBin;
|
|
25
|
+
private readonly configuredPassword;
|
|
26
|
+
private readonly statePath;
|
|
27
|
+
private readonly logPath;
|
|
28
|
+
private readonly logger;
|
|
29
|
+
private readonly childEnv;
|
|
30
|
+
private readonly normalizer;
|
|
31
|
+
private client;
|
|
32
|
+
private child;
|
|
33
|
+
private serverState;
|
|
34
|
+
private connected;
|
|
35
|
+
private sseAbort;
|
|
36
|
+
private sseLoop;
|
|
37
|
+
private readonly pollers;
|
|
38
|
+
private desiredRunning;
|
|
39
|
+
private starting;
|
|
40
|
+
private reconnectTimer;
|
|
41
|
+
constructor(cliBin: string, configuredPassword: string | null, statePath: string, logPath: string, logger: Logger, childEnv?: NodeJS.ProcessEnv | null);
|
|
42
|
+
getClient(): OpencodeClient;
|
|
43
|
+
isConnected(): boolean;
|
|
44
|
+
getServerStatus(): OpencodeServerRuntimeStatus;
|
|
45
|
+
start(): Promise<void>;
|
|
46
|
+
stop(options?: {
|
|
47
|
+
terminateServer?: boolean;
|
|
48
|
+
}): Promise<void>;
|
|
49
|
+
restart(): Promise<void>;
|
|
50
|
+
watchSessionUntilIdle(sessionId: string, directory: string): void;
|
|
51
|
+
recoverPendingRequests(directories: readonly string[]): Promise<void>;
|
|
52
|
+
private pollSession;
|
|
53
|
+
private attachPersistedServer;
|
|
54
|
+
private spawnServer;
|
|
55
|
+
private adoptServer;
|
|
56
|
+
private startSseLoop;
|
|
57
|
+
private runSseLoop;
|
|
58
|
+
private acceptEvent;
|
|
59
|
+
private recoverPendingForDirectory;
|
|
60
|
+
private handleDisconnect;
|
|
61
|
+
private scheduleReconnect;
|
|
62
|
+
private writeState;
|
|
63
|
+
private readState;
|
|
64
|
+
private clearStateForPid;
|
|
65
|
+
}
|
|
66
|
+
export declare function formatSdkError(error: unknown): string;
|