@foxden-app/foxclaw 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +9 -8
- package/CHANGELOG.md +25 -0
- package/dist/antigravity/adapter.d.ts +2 -1
- package/dist/antigravity/adapter.js +180 -42
- package/dist/antigravity/auth.d.ts +5 -0
- package/dist/antigravity/auth.js +37 -0
- package/dist/antigravity/client.js +8 -2
- package/dist/antigravity/controller.d.ts +5 -0
- package/dist/antigravity/controller.js +113 -28
- package/dist/antigravity/events.d.ts +1 -0
- package/dist/antigravity/events.js +29 -0
- package/dist/antigravity/runtime.d.ts +2 -0
- package/dist/antigravity/subagents.d.ts +66 -0
- package/dist/antigravity/subagents.js +459 -0
- package/dist/config.d.ts +3 -1
- package/dist/config.js +36 -29
- package/dist/controller/controller.d.ts +4 -0
- package/dist/controller/controller.js +71 -0
- package/dist/core/engine_spi.d.ts +1 -0
- package/dist/core/orchestrator.d.ts +6 -1
- package/dist/core/orchestrator.js +291 -139
- package/dist/main.js +346 -551
- package/dist/opencode/adapter.js +1 -0
- package/dist/opencode/controller.js +2 -2
- package/dist/store/database.d.ts +3 -0
- package/dist/store/database.js +33 -0
- package/dist/telegram/api.d.ts +1 -0
- package/dist/telegram/api.js +60 -12
- package/dist/telegram/bot_home.js +5 -4
- package/dist/types.d.ts +2 -0
- package/dist/update.js +17 -1
- package/package.json +1 -1
|
@@ -12,6 +12,7 @@ import { renderStreamPreviewContent, buildFoldedToolsSummary, combineSummaryAndR
|
|
|
12
12
|
import { BRIDGE_SCOPE_TELEGRAM_PREFIX, BRIDGE_SCOPE_WEIXIN_PREFIX, parseTelegramTargetFromBridgeScope } from './bridge_scope.js';
|
|
13
13
|
import { escapeTelegramHtml } from '../telegram/html.js';
|
|
14
14
|
import { formatTokenUsageSummary, formatBackendTokenUsageBreakdown } from '../store/token_usage.js';
|
|
15
|
+
import { isTransientNetworkError } from '../telegram/api.js';
|
|
15
16
|
export const STREAM_THROTTLE_MS = 700;
|
|
16
17
|
export const TYPING_INTERVAL_MS = 4000;
|
|
17
18
|
export class UnifiedChannelOrchestrator {
|
|
@@ -299,6 +300,16 @@ export class UnifiedChannelOrchestrator {
|
|
|
299
300
|
defaultEngine: this.adapter.id,
|
|
300
301
|
backends: Array.from(this.backends.keys()),
|
|
301
302
|
});
|
|
303
|
+
// Requeue any interrupted turns from previous crash/restart
|
|
304
|
+
try {
|
|
305
|
+
const requeued = this.store.requeueInterruptedQueuedTurnInputs();
|
|
306
|
+
if (requeued > 0) {
|
|
307
|
+
this.logger.info('orchestrator.requeued_interrupted_turns', { count: requeued });
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
catch (err) {
|
|
311
|
+
this.logger.warn('orchestrator.requeue_interrupted_error', { error: String(err) });
|
|
312
|
+
}
|
|
302
313
|
// Crash recovery: check interrupted preview messages from before restart
|
|
303
314
|
try {
|
|
304
315
|
const backendIds = new Set(this.backends.keys());
|
|
@@ -312,9 +323,19 @@ export class UnifiedChannelOrchestrator {
|
|
|
312
323
|
}
|
|
313
324
|
return false;
|
|
314
325
|
});
|
|
326
|
+
const recoveredScopes = new Set();
|
|
315
327
|
for (const prev of activePreviews) {
|
|
316
328
|
this.store.removeActiveTurnPreview(prev.turnId);
|
|
317
|
-
|
|
329
|
+
recoveredScopes.add(prev.scopeId);
|
|
330
|
+
// Bind existing threadId immediately so auto-resume continues in the exact same thread
|
|
331
|
+
if (prev.threadId) {
|
|
332
|
+
const currentBinding = this.store.getBinding(prev.scopeId);
|
|
333
|
+
this.store.setBinding(prev.scopeId, prev.threadId, currentBinding?.cwd ?? this.config.defaultCwd);
|
|
334
|
+
}
|
|
335
|
+
const locale = this.store.getChatSettings(prev.scopeId)?.locale || 'zh';
|
|
336
|
+
this.editMessage(prev.scopeId, prev.messageId, locale === 'zh'
|
|
337
|
+
? '🔄 **[Foxclaw 服务已重新加载/重启]**\n\n检测到上一轮任务执行被服务重启中断,正在自动继续执行…'
|
|
338
|
+
: '🔄 **[FoxClaw Service Reloaded / Restarted]**\n\nInterrupted turn detected. Resuming automatically…').catch(() => { });
|
|
318
339
|
const fakeEvent = {
|
|
319
340
|
scopeId: prev.scopeId,
|
|
320
341
|
chatId: prev.scopeId,
|
|
@@ -327,8 +348,14 @@ export class UnifiedChannelOrchestrator {
|
|
|
327
348
|
entities: [],
|
|
328
349
|
replyToBot: false,
|
|
329
350
|
};
|
|
351
|
+
const resumePrompt = prev.threadId
|
|
352
|
+
? (locale === 'zh' ? 'FoxClaw 服务重启中断,请在当前会话中从中断处继续完成刚才的工作。' : 'FoxClaw restarted while previous turn was running. Please resume and complete the work from where it left off.')
|
|
353
|
+
: (locale === 'zh' ? '请继续完成上一步未完成的任务。' : 'Please continue and complete the unfinished task from previous step.');
|
|
330
354
|
const resumeTimer = setTimeout(() => {
|
|
331
|
-
this.startPromptTurn(fakeEvent,
|
|
355
|
+
this.startPromptTurn(fakeEvent, resumePrompt, locale, {
|
|
356
|
+
reuseMessageId: prev.messageId,
|
|
357
|
+
forcedThreadId: prev.threadId || undefined,
|
|
358
|
+
}).catch((err) => {
|
|
332
359
|
this.logger.warn('orchestrator.auto_resume_failed', {
|
|
333
360
|
scopeId: prev.scopeId,
|
|
334
361
|
error: String(err),
|
|
@@ -337,6 +364,32 @@ export class UnifiedChannelOrchestrator {
|
|
|
337
364
|
}, 1500);
|
|
338
365
|
resumeTimer?.unref?.();
|
|
339
366
|
}
|
|
367
|
+
// Check for queued turns on scopes without active previews
|
|
368
|
+
const queuedInputs = this.store.listQueuedTurnInputs();
|
|
369
|
+
for (const q of queuedInputs) {
|
|
370
|
+
if (!recoveredScopes.has(q.scopeId) && !this.activeTurns.has(q.scopeId)) {
|
|
371
|
+
recoveredScopes.add(q.scopeId);
|
|
372
|
+
const locale = this.store.getChatSettings(q.scopeId)?.locale || 'zh';
|
|
373
|
+
const fakeEvent = {
|
|
374
|
+
scopeId: q.scopeId,
|
|
375
|
+
chatId: q.chatId,
|
|
376
|
+
topicId: q.topicId ?? null,
|
|
377
|
+
chatType: q.chatType || 'private',
|
|
378
|
+
userId: 'system',
|
|
379
|
+
messageId: q.messageId ?? 0,
|
|
380
|
+
text: q.sourceSummary,
|
|
381
|
+
attachments: [],
|
|
382
|
+
entities: [],
|
|
383
|
+
replyToBot: false,
|
|
384
|
+
};
|
|
385
|
+
const drainTimer = setTimeout(() => {
|
|
386
|
+
this.drainNextQueuedTurn(fakeEvent, locale).catch((err) => {
|
|
387
|
+
this.logger.warn('orchestrator.auto_drain_failed', { scopeId: q.scopeId, error: String(err) });
|
|
388
|
+
});
|
|
389
|
+
}, 2000);
|
|
390
|
+
drainTimer?.unref?.();
|
|
391
|
+
}
|
|
392
|
+
}
|
|
340
393
|
}
|
|
341
394
|
catch (err) {
|
|
342
395
|
this.logger.warn('orchestrator.restore_active_turns_error', { error: String(err) });
|
|
@@ -370,8 +423,36 @@ export class UnifiedChannelOrchestrator {
|
|
|
370
423
|
return lastMsgId;
|
|
371
424
|
}
|
|
372
425
|
async editMessage(scopeId, messageId, text, keyboard) {
|
|
426
|
+
if (!messageId || messageId <= 0) {
|
|
427
|
+
await this.sendMessage(scopeId, text, keyboard);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
373
430
|
await this.messaging.editRichMarkdown(scopeId, messageId, text, keyboard);
|
|
374
431
|
}
|
|
432
|
+
async safeDeliverChunks(scopeId, messageId, chunks) {
|
|
433
|
+
if (!chunks || chunks.length === 0)
|
|
434
|
+
return;
|
|
435
|
+
try {
|
|
436
|
+
if (messageId > 0) {
|
|
437
|
+
await this.editMessage(scopeId, messageId, chunks[0]).catch(async () => {
|
|
438
|
+
await this.sendMessage(scopeId, chunks[0]).catch(() => { });
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
await this.sendMessage(scopeId, chunks[0]).catch(() => { });
|
|
443
|
+
}
|
|
444
|
+
for (let i = 1; i < chunks.length; i++) {
|
|
445
|
+
await this.sendMessage(scopeId, chunks[i]).catch(() => { });
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
catch (err) {
|
|
449
|
+
this.logger.warn('orchestrator.safe_deliver_chunks_failed', { error: String(err) });
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
async safeDeliverMessage(scopeId, messageId, text) {
|
|
453
|
+
const chunks = chunkTelegramMessage(text);
|
|
454
|
+
await this.safeDeliverChunks(scopeId, messageId, chunks);
|
|
455
|
+
}
|
|
375
456
|
async handleText(event) {
|
|
376
457
|
const scopeId = event.scopeId;
|
|
377
458
|
const locale = this.store.getChatSettings(scopeId)?.locale ?? 'zh';
|
|
@@ -423,7 +504,7 @@ export class UnifiedChannelOrchestrator {
|
|
|
423
504
|
return;
|
|
424
505
|
case 'model':
|
|
425
506
|
if (argsString) {
|
|
426
|
-
this.store.
|
|
507
|
+
this.store.setChatModel(scopeId, argsString === 'default' ? null : argsString);
|
|
427
508
|
this.syncCurrentBackendSettings(scopeId);
|
|
428
509
|
await this.sendMessage(scopeId, locale === 'zh' ? `✅ 模型已切换为: \`${argsString}\`` : `✅ Model switched to: \`${argsString}\``);
|
|
429
510
|
}
|
|
@@ -500,7 +581,7 @@ export class UnifiedChannelOrchestrator {
|
|
|
500
581
|
? data.slice('engine:m:'.length)
|
|
501
582
|
: decodeURIComponent(data.slice('setup:model:'.length));
|
|
502
583
|
const model = rawModel === 'default' ? null : rawModel;
|
|
503
|
-
this.store.
|
|
584
|
+
this.store.setChatModel(scopeId, model);
|
|
504
585
|
this.syncCurrentBackendSettings(scopeId);
|
|
505
586
|
await this.messaging.answerCallback(event.callbackQueryId, `Model: ${rawModel}`);
|
|
506
587
|
await this.sendSetupMenu(scopeId, locale, messageId);
|
|
@@ -515,7 +596,7 @@ export class UnifiedChannelOrchestrator {
|
|
|
515
596
|
if (targetEffort !== 'high' && settings?.serviceTier === 'boost') {
|
|
516
597
|
this.store.setChatServiceTier(scopeId, null);
|
|
517
598
|
}
|
|
518
|
-
this.store.
|
|
599
|
+
this.store.setChatEffort(scopeId, targetEffort);
|
|
519
600
|
this.syncCurrentBackendSettings(scopeId);
|
|
520
601
|
await this.messaging.answerCallback(event.callbackQueryId, `Effort: ${rawEffort}`);
|
|
521
602
|
await this.sendSetupMenu(scopeId, locale, messageId);
|
|
@@ -533,7 +614,7 @@ export class UnifiedChannelOrchestrator {
|
|
|
533
614
|
const nextBoost = !isBoost;
|
|
534
615
|
this.store.setChatServiceTier(scopeId, nextBoost ? 'boost' : null);
|
|
535
616
|
if (nextBoost) {
|
|
536
|
-
this.store.
|
|
617
|
+
this.store.setChatEffort(scopeId, 'high');
|
|
537
618
|
}
|
|
538
619
|
this.syncCurrentBackendSettings(scopeId);
|
|
539
620
|
await this.messaging.answerCallback(event.callbackQueryId, nextBoost ? (locale === 'zh' ? '🚀 Boost 模式已开启 (深度 High)' : '🚀 Boost Mode enabled (High)') : (locale === 'zh' ? '⚪ Boost 模式已关闭' : '⚪ Boost Mode disabled'));
|
|
@@ -567,11 +648,11 @@ export class UnifiedChannelOrchestrator {
|
|
|
567
648
|
}
|
|
568
649
|
}
|
|
569
650
|
}
|
|
570
|
-
async startPromptTurn(event, prompt, locale) {
|
|
651
|
+
async startPromptTurn(event, prompt, locale, options) {
|
|
571
652
|
const scopeId = event.scopeId;
|
|
572
653
|
const binding = this.store.getBinding(scopeId);
|
|
573
654
|
const cwd = binding?.cwd || this.config.defaultCwd;
|
|
574
|
-
const threadId = binding?.threadId
|
|
655
|
+
const threadId = options?.forcedThreadId ?? binding?.threadId ?? 'default';
|
|
575
656
|
let effectivePrompt = prompt;
|
|
576
657
|
let stagedAttachments;
|
|
577
658
|
if (event.attachments && event.attachments.length > 0) {
|
|
@@ -600,13 +681,13 @@ export class UnifiedChannelOrchestrator {
|
|
|
600
681
|
await this.sendMessage(scopeId, locale === 'zh'
|
|
601
682
|
? `⚡ **已插话中断前置任务,立即开始新指令**:\n> ${effectivePrompt}`
|
|
602
683
|
: `⚡ **Interrupted previous turn, executing new instruction**:\n> ${effectivePrompt}`);
|
|
603
|
-
await this.executeTurn(event, effectivePrompt, locale, 0, stagedAttachments);
|
|
684
|
+
await this.executeTurn(event, effectivePrompt, locale, 0, stagedAttachments, options);
|
|
604
685
|
return;
|
|
605
686
|
}
|
|
606
687
|
await this.enqueuePromptTurn(event, effectivePrompt, locale);
|
|
607
688
|
return;
|
|
608
689
|
}
|
|
609
|
-
await this.executeTurn(event, effectivePrompt, locale, 0, stagedAttachments);
|
|
690
|
+
await this.executeTurn(event, effectivePrompt, locale, 0, stagedAttachments, options);
|
|
610
691
|
}
|
|
611
692
|
async enqueuePromptTurn(event, prompt, locale) {
|
|
612
693
|
const scopeId = event.scopeId;
|
|
@@ -663,22 +744,41 @@ export class UnifiedChannelOrchestrator {
|
|
|
663
744
|
this.logger.warn('orchestrator.drain_queue_failed', { error: String(err) });
|
|
664
745
|
}
|
|
665
746
|
}
|
|
666
|
-
async executeTurn(event, prompt, locale, retryCount = 0, stagedAttachments) {
|
|
747
|
+
async executeTurn(event, prompt, locale, retryCount = 0, stagedAttachments, options) {
|
|
667
748
|
const scopeId = event.scopeId;
|
|
668
749
|
const binding = this.store.getBinding(scopeId);
|
|
669
750
|
const settings = this.store.getChatSettings(scopeId);
|
|
670
751
|
const adapter = this.getAdapterForScope(scopeId);
|
|
671
752
|
const cwd = binding?.cwd || this.config.defaultCwd;
|
|
672
|
-
const threadId = binding?.threadId
|
|
753
|
+
const threadId = options?.forcedThreadId ?? binding?.threadId ?? null;
|
|
673
754
|
const model = settings?.model || 'default';
|
|
674
755
|
const isBoost = settings?.serviceTier === 'boost';
|
|
675
756
|
const effort = isBoost ? 'high' : (settings?.reasoningEffort ?? 'high');
|
|
676
757
|
const effectivePrompt = isBoost && !prompt.startsWith('[Boost Mode:')
|
|
677
758
|
? `[Boost Mode: Proceed with deep thinking, strategic planning, multiple perspectives, and rigorous verification.]\n\n${prompt}`
|
|
678
759
|
: prompt;
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
760
|
+
let initialMsgId = 0;
|
|
761
|
+
if (options?.reuseMessageId) {
|
|
762
|
+
initialMsgId = options.reuseMessageId;
|
|
763
|
+
try {
|
|
764
|
+
await this.editMessage(scopeId, initialMsgId, locale === 'zh'
|
|
765
|
+
? `🔄 **[重启恢复] ${adapter.name} 正在继续执行…**`
|
|
766
|
+
: `🔄 **[Resuming after restart] ${adapter.name} is continuing…**`);
|
|
767
|
+
}
|
|
768
|
+
catch (editErr) {
|
|
769
|
+
this.logger.warn('orchestrator.reuse_message_failed', { error: String(editErr) });
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
else {
|
|
773
|
+
try {
|
|
774
|
+
initialMsgId = await this.sendMessage(scopeId, locale === 'zh'
|
|
775
|
+
? (isBoost ? `🚀 ${adapter.name} (Boost 模式) 正在深度思考中…` : `⏳ ${adapter.name} 正在思考中…`)
|
|
776
|
+
: (isBoost ? `🚀 ${adapter.name} (Boost Mode) is thinking deeply…` : `⏳ ${adapter.name} is thinking…`));
|
|
777
|
+
}
|
|
778
|
+
catch (sendErr) {
|
|
779
|
+
this.logger.warn('orchestrator.initial_message_failed', { error: String(sendErr) });
|
|
780
|
+
}
|
|
781
|
+
}
|
|
682
782
|
const turnKey = `${adapter.id}_${scopeId}_${Date.now()}`;
|
|
683
783
|
try {
|
|
684
784
|
this.store.saveActiveTurnPreview({
|
|
@@ -786,79 +886,47 @@ export class UnifiedChannelOrchestrator {
|
|
|
786
886
|
}
|
|
787
887
|
scheduleFlush();
|
|
788
888
|
});
|
|
889
|
+
execution.on('conversation', (convId) => {
|
|
890
|
+
if (!convId)
|
|
891
|
+
return;
|
|
892
|
+
activeTurn.threadId = convId;
|
|
893
|
+
this.store.setBinding(scopeId, convId, cwd);
|
|
894
|
+
this.store.saveActiveTurnPreview({
|
|
895
|
+
turnId: turnKey,
|
|
896
|
+
scopeId,
|
|
897
|
+
threadId: convId,
|
|
898
|
+
messageId: activeTurn.messageId,
|
|
899
|
+
});
|
|
900
|
+
this.syncCurrentBackendSettings(scopeId);
|
|
901
|
+
});
|
|
789
902
|
const retryTurn = async (nextRetryCount) => {
|
|
790
|
-
await this.executeTurn(event, prompt, locale, nextRetryCount, stagedAttachments);
|
|
903
|
+
await this.executeTurn(event, prompt, locale, nextRetryCount, stagedAttachments, options);
|
|
791
904
|
};
|
|
792
905
|
execution.on('result', async (res) => {
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
if (res.usage) {
|
|
804
|
-
this.store.recordTokenUsage({
|
|
805
|
-
inputTokens: res.usage.inputTokens,
|
|
806
|
-
outputTokens: res.usage.outputTokens,
|
|
807
|
-
cachedTokens: res.usage.cachedTokens,
|
|
808
|
-
totalTokens: res.usage.totalTokens,
|
|
809
|
-
}, adapter.id);
|
|
810
|
-
}
|
|
811
|
-
if (res.status === 'SUCCESS') {
|
|
812
|
-
let finalText = (res.response || '').trim();
|
|
813
|
-
if (!finalText && activeTurn.accumulatedText) {
|
|
814
|
-
finalText = activeTurn.accumulatedText.trim();
|
|
815
|
-
}
|
|
816
|
-
const foldedTools = buildFoldedToolsSummary({
|
|
817
|
-
stepIndex: activeTurn.stepIndex,
|
|
818
|
-
toolLines: activeTurn.toolLines,
|
|
819
|
-
toolCount: activeTurn.toolCount || activeTurn.toolLines.length,
|
|
820
|
-
startTime: activeTurn.startTime,
|
|
821
|
-
usage: res.usage,
|
|
822
|
-
locale,
|
|
823
|
-
});
|
|
824
|
-
const chunks = combineSummaryAndResponse(foldedTools, finalText || '(无输出 / No output)');
|
|
825
|
-
if (chunks.length > 0) {
|
|
826
|
-
await this.editMessage(scopeId, activeTurn.messageId, chunks[0]).catch(() => {
|
|
827
|
-
return this.sendMessage(scopeId, chunks[0]);
|
|
828
|
-
});
|
|
829
|
-
for (let i = 1; i < chunks.length; i++) {
|
|
830
|
-
await this.sendMessage(scopeId, chunks[i]);
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
await this.drainNextQueuedTurn(event, locale);
|
|
834
|
-
return;
|
|
835
|
-
}
|
|
836
|
-
if (res.status === 'ERROR') {
|
|
837
|
-
const errorText = (res.error || res.response || res.error || 'Unknown error').trim();
|
|
838
|
-
if (adapter.handleTurnError) {
|
|
839
|
-
const handled = await adapter.handleTurnError({
|
|
840
|
-
error: errorText,
|
|
841
|
-
request: req,
|
|
842
|
-
retryCount,
|
|
843
|
-
retryTurn,
|
|
844
|
-
sendMessage: (text) => this.sendMessage(scopeId, text),
|
|
845
|
-
editMessage: (messageId, text) => this.editMessage(scopeId, messageId, text),
|
|
846
|
-
});
|
|
847
|
-
if (handled)
|
|
848
|
-
return;
|
|
906
|
+
try {
|
|
907
|
+
if (activeTurn.flushTimer)
|
|
908
|
+
clearTimeout(activeTurn.flushTimer);
|
|
909
|
+
if (activeTurn.typingTimer)
|
|
910
|
+
clearInterval(activeTurn.typingTimer);
|
|
911
|
+
this.activeTurns.delete(scopeId);
|
|
912
|
+
this.store.removeActiveTurnPreview(turnKey);
|
|
913
|
+
if (res.conversationId && (!binding?.threadId || binding.threadId !== res.conversationId)) {
|
|
914
|
+
this.store.setBinding(scopeId, res.conversationId, cwd);
|
|
915
|
+
this.syncCurrentBackendSettings(scopeId);
|
|
849
916
|
}
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
917
|
+
if (res.usage) {
|
|
918
|
+
this.store.recordTokenUsage({
|
|
919
|
+
inputTokens: res.usage.inputTokens,
|
|
920
|
+
outputTokens: res.usage.outputTokens,
|
|
921
|
+
cachedTokens: res.usage.cachedTokens,
|
|
922
|
+
totalTokens: res.usage.totalTokens,
|
|
923
|
+
}, adapter.id);
|
|
854
924
|
}
|
|
855
|
-
|
|
856
|
-
finalText
|
|
857
|
-
finalText
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
finalText.length <= 20;
|
|
861
|
-
if (!isPureError) {
|
|
925
|
+
if (res.status === 'SUCCESS') {
|
|
926
|
+
let finalText = (res.response || '').trim();
|
|
927
|
+
if (!finalText && activeTurn.accumulatedText) {
|
|
928
|
+
finalText = activeTurn.accumulatedText.trim();
|
|
929
|
+
}
|
|
862
930
|
const foldedTools = buildFoldedToolsSummary({
|
|
863
931
|
stepIndex: activeTurn.stepIndex,
|
|
864
932
|
toolLines: activeTurn.toolLines,
|
|
@@ -867,71 +935,155 @@ export class UnifiedChannelOrchestrator {
|
|
|
867
935
|
usage: res.usage,
|
|
868
936
|
locale,
|
|
869
937
|
});
|
|
870
|
-
const
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
await
|
|
938
|
+
const chunks = combineSummaryAndResponse(foldedTools, finalText || '(无输出 / No output)');
|
|
939
|
+
await this.safeDeliverChunks(scopeId, activeTurn.messageId, chunks);
|
|
940
|
+
await this.drainNextQueuedTurn(event, locale).catch(() => { });
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
if (res.status === 'ERROR') {
|
|
944
|
+
const errorText = (res.error || res.response || res.error || 'Unknown error').trim();
|
|
945
|
+
if (adapter.handleTurnError) {
|
|
946
|
+
try {
|
|
947
|
+
const handled = await adapter.handleTurnError({
|
|
948
|
+
error: errorText,
|
|
949
|
+
request: req,
|
|
950
|
+
retryCount,
|
|
951
|
+
retryTurn,
|
|
952
|
+
sendMessage: (text) => this.sendMessage(scopeId, text),
|
|
953
|
+
editMessage: (messageId, text) => this.editMessage(scopeId, messageId, text),
|
|
954
|
+
});
|
|
955
|
+
if (handled)
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
catch (adapterErr) {
|
|
959
|
+
this.logger.warn('orchestrator.adapter_handle_turn_error_failed', { error: String(adapterErr) });
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
if (isTransientNetworkError(errorText) && retryCount < 3) {
|
|
963
|
+
const backoffMs = Math.min(15000, 3000 * Math.pow(2, retryCount));
|
|
964
|
+
const delaySec = Math.round(backoffMs / 1000);
|
|
965
|
+
try {
|
|
966
|
+
await this.sendMessage(scopeId, locale === 'zh'
|
|
967
|
+
? `🌐 **网络波动重试 (第 ${retryCount + 1}/3 次)**: 正在等待网络稳定 (${delaySec}s) 后自动恢复任务…`
|
|
968
|
+
: `🌐 **Network Retry (${retryCount + 1}/3)**: Waiting for connection (${delaySec}s) to resume task…`);
|
|
880
969
|
}
|
|
970
|
+
catch {
|
|
971
|
+
/* ignore messaging error during network glitch */
|
|
972
|
+
}
|
|
973
|
+
await new Promise((r) => setTimeout(r, backoffMs));
|
|
974
|
+
try {
|
|
975
|
+
await retryTurn(retryCount + 1);
|
|
976
|
+
}
|
|
977
|
+
catch (retryErr) {
|
|
978
|
+
this.logger.error('orchestrator.retry_turn_failed', { error: String(retryErr) });
|
|
979
|
+
}
|
|
980
|
+
return;
|
|
881
981
|
}
|
|
882
|
-
|
|
982
|
+
// If the agent actually produced substantive response text, do NOT mask it with an error box!
|
|
983
|
+
let finalText = (res.response || '').trim();
|
|
984
|
+
if (!finalText && activeTurn.accumulatedText) {
|
|
985
|
+
finalText = activeTurn.accumulatedText.trim();
|
|
986
|
+
}
|
|
987
|
+
const isPureError = !finalText ||
|
|
988
|
+
finalText === errorText ||
|
|
989
|
+
finalText.startsWith('Process exited with code') ||
|
|
990
|
+
finalText.startsWith('Verification Required') ||
|
|
991
|
+
finalText.startsWith('Error:') ||
|
|
992
|
+
finalText.length <= 20;
|
|
993
|
+
if (!isPureError) {
|
|
994
|
+
const foldedTools = buildFoldedToolsSummary({
|
|
995
|
+
stepIndex: activeTurn.stepIndex,
|
|
996
|
+
toolLines: activeTurn.toolLines,
|
|
997
|
+
toolCount: activeTurn.toolCount || activeTurn.toolLines.length,
|
|
998
|
+
startTime: activeTurn.startTime,
|
|
999
|
+
usage: res.usage,
|
|
1000
|
+
locale,
|
|
1001
|
+
});
|
|
1002
|
+
const warningNote = errorText && errorText !== 'Unknown error' && errorText !== 'Antigravity execution failed'
|
|
1003
|
+
? `\n\n⚠️ <i>(注意:任务结束时伴随提示: ${escapeTelegramHtml(errorText.slice(0, 120))})</i>`
|
|
1004
|
+
: '';
|
|
1005
|
+
const chunks = combineSummaryAndResponse(foldedTools, finalText + warningNote);
|
|
1006
|
+
await this.safeDeliverChunks(scopeId, activeTurn.messageId, chunks);
|
|
1007
|
+
await this.drainNextQueuedTurn(event, locale).catch(() => { });
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
await this.safeDeliverMessage(scopeId, activeTurn.messageId, `❌ **${adapter.name} 错误**:\n\`\`\`\n${errorText}\n\`\`\``);
|
|
1011
|
+
await this.drainNextQueuedTurn(event, locale).catch(() => { });
|
|
883
1012
|
return;
|
|
884
1013
|
}
|
|
885
|
-
await this.
|
|
886
|
-
await this.drainNextQueuedTurn(event, locale);
|
|
887
|
-
|
|
1014
|
+
await this.safeDeliverMessage(scopeId, activeTurn.messageId, activeTurn.accumulatedText || '⚠️ 执行结束');
|
|
1015
|
+
await this.drainNextQueuedTurn(event, locale).catch(() => { });
|
|
1016
|
+
}
|
|
1017
|
+
catch (fatalErr) {
|
|
1018
|
+
this.logger.error('orchestrator.turn_result_unhandled', { error: String(fatalErr) });
|
|
888
1019
|
}
|
|
889
|
-
await this.editMessage(scopeId, activeTurn.messageId, activeTurn.accumulatedText || '⚠️ 执行结束');
|
|
890
|
-
await this.drainNextQueuedTurn(event, locale);
|
|
891
1020
|
});
|
|
892
1021
|
execution.on('error', async (err) => {
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
1022
|
+
try {
|
|
1023
|
+
if (activeTurn.flushTimer)
|
|
1024
|
+
clearTimeout(activeTurn.flushTimer);
|
|
1025
|
+
if (activeTurn.typingTimer)
|
|
1026
|
+
clearInterval(activeTurn.typingTimer);
|
|
1027
|
+
this.activeTurns.delete(scopeId);
|
|
1028
|
+
this.store.removeActiveTurnPreview(turnKey);
|
|
1029
|
+
if (adapter.handleTurnError) {
|
|
1030
|
+
try {
|
|
1031
|
+
const handled = await adapter.handleTurnError({
|
|
1032
|
+
error: err.message,
|
|
1033
|
+
request: req,
|
|
1034
|
+
retryCount,
|
|
1035
|
+
retryTurn,
|
|
1036
|
+
sendMessage: (text) => this.sendMessage(scopeId, text),
|
|
1037
|
+
editMessage: (messageId, text) => this.editMessage(scopeId, messageId, text),
|
|
1038
|
+
});
|
|
1039
|
+
if (handled)
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
catch (adapterErr) {
|
|
1043
|
+
this.logger.warn('orchestrator.adapter_handle_error_failed', { error: String(adapterErr) });
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
if (isTransientNetworkError(err.message) && retryCount < 3) {
|
|
1047
|
+
const backoffMs = Math.min(15000, 3000 * Math.pow(2, retryCount));
|
|
1048
|
+
const delaySec = Math.round(backoffMs / 1000);
|
|
1049
|
+
try {
|
|
1050
|
+
await this.sendMessage(scopeId, locale === 'zh'
|
|
1051
|
+
? `🌐 **网络波动重试 (第 ${retryCount + 1}/3 次)**: 正在等待网络稳定 (${delaySec}s) 后自动恢复任务…`
|
|
1052
|
+
: `🌐 **Network Retry (${retryCount + 1}/3)**: Waiting for connection (${delaySec}s) to resume task…`);
|
|
1053
|
+
}
|
|
1054
|
+
catch {
|
|
1055
|
+
/* ignore */
|
|
1056
|
+
}
|
|
1057
|
+
await new Promise((r) => setTimeout(r, backoffMs));
|
|
1058
|
+
try {
|
|
1059
|
+
await retryTurn(retryCount + 1);
|
|
1060
|
+
}
|
|
1061
|
+
catch (retryErr) {
|
|
1062
|
+
this.logger.error('orchestrator.retry_turn_failed', { error: String(retryErr) });
|
|
1063
|
+
}
|
|
909
1064
|
return;
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
});
|
|
919
|
-
const fullAnswer = activeTurn.accumulatedText.trim() +
|
|
920
|
-
`\n\n⚠️ <i>(注意:任务执行中途异常中断: ${escapeTelegramHtml(err.message.slice(0, 120))})</i>`;
|
|
921
|
-
const chunks = combineSummaryAndResponse(foldedTools, fullAnswer);
|
|
922
|
-
if (chunks.length > 0) {
|
|
923
|
-
await this.editMessage(scopeId, activeTurn.messageId, chunks[0]).catch(() => {
|
|
924
|
-
return this.sendMessage(scopeId, chunks[0]);
|
|
1065
|
+
}
|
|
1066
|
+
if (activeTurn.accumulatedText && activeTurn.accumulatedText.trim().length > 20) {
|
|
1067
|
+
const foldedTools = buildFoldedToolsSummary({
|
|
1068
|
+
stepIndex: activeTurn.stepIndex,
|
|
1069
|
+
toolLines: activeTurn.toolLines,
|
|
1070
|
+
toolCount: activeTurn.toolCount || activeTurn.toolLines.length,
|
|
1071
|
+
startTime: activeTurn.startTime,
|
|
1072
|
+
locale,
|
|
925
1073
|
});
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1074
|
+
const fullAnswer = activeTurn.accumulatedText.trim() +
|
|
1075
|
+
`\n\n⚠️ <i>(注意:任务执行中途异常中断: ${escapeTelegramHtml(err.message.slice(0, 120))})</i>`;
|
|
1076
|
+
const chunks = combineSummaryAndResponse(foldedTools, fullAnswer);
|
|
1077
|
+
await this.safeDeliverChunks(scopeId, activeTurn.messageId, chunks);
|
|
1078
|
+
await this.drainNextQueuedTurn(event, locale).catch(() => { });
|
|
1079
|
+
return;
|
|
929
1080
|
}
|
|
930
|
-
await this.
|
|
931
|
-
|
|
1081
|
+
await this.safeDeliverMessage(scopeId, activeTurn.messageId, `❌ **执行异常**:\n\`\`\`\n${err.message}\n\`\`\``);
|
|
1082
|
+
await this.drainNextQueuedTurn(event, locale).catch(() => { });
|
|
1083
|
+
}
|
|
1084
|
+
catch (fatalErr) {
|
|
1085
|
+
this.logger.error('orchestrator.turn_error_unhandled', { error: String(fatalErr) });
|
|
932
1086
|
}
|
|
933
|
-
await this.editMessage(scopeId, activeTurn.messageId, `❌ **执行异常**:\n\`\`\`\n${err.message}\n\`\`\``);
|
|
934
|
-
await this.drainNextQueuedTurn(event, locale);
|
|
935
1087
|
});
|
|
936
1088
|
}
|
|
937
1089
|
async handleEffortCommand(scopeId, args, locale) {
|
|
@@ -941,7 +1093,7 @@ export class UnifiedChannelOrchestrator {
|
|
|
941
1093
|
if (target !== 'high' && settings?.serviceTier === 'boost') {
|
|
942
1094
|
this.store.setChatServiceTier(scopeId, null);
|
|
943
1095
|
}
|
|
944
|
-
this.store.
|
|
1096
|
+
this.store.setChatEffort(scopeId, target);
|
|
945
1097
|
this.syncCurrentBackendSettings(scopeId);
|
|
946
1098
|
await this.sendMessage(scopeId, locale === 'zh' ? `✅ 思考深度已设置为: \`${target}\`` : `✅ Reasoning effort set to: \`${target}\``);
|
|
947
1099
|
}
|
|
@@ -962,7 +1114,7 @@ export class UnifiedChannelOrchestrator {
|
|
|
962
1114
|
nextBoost = false;
|
|
963
1115
|
this.store.setChatServiceTier(scopeId, nextBoost ? 'boost' : null);
|
|
964
1116
|
if (nextBoost) {
|
|
965
|
-
this.store.
|
|
1117
|
+
this.store.setChatEffort(scopeId, 'high');
|
|
966
1118
|
}
|
|
967
1119
|
this.syncCurrentBackendSettings(scopeId);
|
|
968
1120
|
const msg = locale === 'zh'
|