@foxden-app/foxclaw 0.11.1 → 0.13.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 +18 -1
- package/CHANGELOG.md +36 -0
- package/README.md +4 -3
- package/README_EN.md +4 -3
- package/dist/antigravity/account_ui.d.ts +34 -0
- package/dist/antigravity/account_ui.js +354 -0
- package/dist/antigravity/adapter.js +1 -1
- package/dist/antigravity/auth.d.ts +4 -1
- package/dist/antigravity/auth.js +17 -5
- package/dist/antigravity/controller.d.ts +3 -106
- package/dist/antigravity/controller.js +2 -2083
- package/dist/antigravity/conversation_ui.d.ts +56 -0
- package/dist/antigravity/conversation_ui.js +509 -0
- package/dist/antigravity/ui_callbacks.d.ts +6 -0
- package/dist/antigravity/ui_callbacks.js +6 -0
- package/dist/bridge/unified_bridge.d.ts +114 -0
- package/dist/bridge/unified_bridge.js +1343 -0
- package/dist/channels/bridge_messaging_router.d.ts +8 -4
- package/dist/channels/bridge_messaging_router.js +15 -4
- package/dist/channels/telegram/telegram_messaging_port.d.ts +16 -0
- package/dist/channels/telegram/telegram_messaging_port.js +22 -2
- package/dist/channels/text_actions.d.ts +14 -0
- package/dist/channels/text_actions.js +48 -0
- package/dist/channels/weixin/weixin_channel_adapter.d.ts +3 -4
- package/dist/channels/weixin/weixin_channel_adapter.js +2 -2
- package/dist/channels/weixin/weixin_messaging_port.d.ts +16 -1
- package/dist/channels/weixin/weixin_messaging_port.js +34 -7
- package/dist/codex_app/activity.d.ts +62 -0
- package/dist/codex_app/activity.js +418 -0
- package/dist/codex_app/adapter.d.ts +2 -2
- package/dist/codex_app/adapter.js +55 -25
- package/dist/codex_app/backend_ui.d.ts +4 -0
- package/dist/codex_app/backend_ui.js +48 -0
- package/dist/codex_app/client.js +0 -12
- package/dist/config.d.ts +4 -0
- package/dist/config.js +41 -5
- package/dist/controller/access.d.ts +1 -11
- package/dist/controller/access.js +1 -33
- package/dist/controller/activity.d.ts +1 -62
- package/dist/controller/activity.js +2 -418
- package/dist/controller/auth_errors.d.ts +11 -0
- package/dist/controller/auth_errors.js +124 -0
- package/dist/controller/auth_files.d.ts +13 -0
- package/dist/controller/auth_files.js +156 -0
- package/dist/controller/auth_presentation.d.ts +77 -0
- package/dist/controller/auth_presentation.js +697 -0
- package/dist/controller/auth_quota_service.d.ts +27 -0
- package/dist/controller/auth_quota_service.js +151 -0
- package/dist/controller/controller.d.ts +30 -56
- package/dist/controller/controller.js +181 -3497
- package/dist/controller/interactions.d.ts +74 -0
- package/dist/controller/interactions.js +560 -0
- package/dist/controller/local_usage_service.d.ts +19 -0
- package/dist/controller/local_usage_service.js +62 -0
- package/dist/controller/native_panel_service.d.ts +55 -0
- package/dist/controller/native_panel_service.js +214 -0
- package/dist/controller/native_panels.d.ts +61 -0
- package/dist/controller/native_panels.js +784 -0
- package/dist/controller/shared_helpers.d.ts +53 -0
- package/dist/controller/shared_helpers.js +399 -0
- package/dist/controller/state_types.d.ts +390 -0
- package/dist/controller/state_types.js +82 -0
- package/dist/controller/turn_rendering.d.ts +22 -0
- package/dist/controller/turn_rendering.js +288 -0
- package/dist/core/access.d.ts +11 -0
- package/dist/core/access.js +33 -0
- package/dist/core/attachment_files.d.ts +13 -0
- package/dist/core/attachment_files.js +180 -0
- package/dist/core/attachment_types.d.ts +22 -0
- package/dist/core/attachment_types.js +1 -0
- package/dist/core/attachments.d.ts +5 -3
- package/dist/core/attachments.js +2 -2
- package/dist/core/backend_registry.d.ts +22 -0
- package/dist/core/backend_registry.js +50 -0
- package/dist/core/backend_ui.d.ts +28 -0
- package/dist/core/backend_ui.js +1 -0
- package/dist/core/channel_events.d.ts +48 -0
- package/dist/core/channel_events.js +1 -0
- package/dist/core/channel_port.d.ts +52 -8
- package/dist/core/engine_spi.d.ts +20 -5
- package/dist/core/listener_scope.d.ts +11 -0
- package/dist/core/listener_scope.js +10 -0
- package/dist/core/message_text.d.ts +2 -0
- package/dist/core/message_text.js +28 -0
- package/dist/core/network_errors.d.ts +1 -0
- package/dist/core/network_errors.js +23 -0
- package/dist/core/orchestrator.d.ts +57 -22
- package/dist/core/orchestrator.js +877 -477
- package/dist/core/queued_engine_input.d.ts +12 -0
- package/dist/core/queued_engine_input.js +16 -0
- package/dist/core/runtime_supervisor.d.ts +19 -0
- package/dist/core/runtime_supervisor.js +45 -0
- package/dist/core/scope_operations.d.ts +7 -0
- package/dist/core/scope_operations.js +19 -0
- package/dist/core/stream_preview.js +21 -9
- package/dist/dsh/adapter.d.ts +48 -0
- package/dist/dsh/adapter.js +205 -0
- package/dist/dsh/backend.d.ts +7 -0
- package/dist/dsh/backend.js +21 -0
- package/dist/dsh/client.d.ts +46 -0
- package/dist/dsh/client.js +184 -0
- package/dist/dsh/permissions_plugin.d.ts +30 -0
- package/dist/dsh/permissions_plugin.js +21 -0
- package/dist/dsh/runtime.d.ts +20 -0
- package/dist/dsh/runtime.js +36 -0
- package/dist/dsh/ui.d.ts +38 -0
- package/dist/dsh/ui.js +342 -0
- package/dist/i18n.d.ts +4 -0
- package/dist/i18n.js +12 -0
- package/dist/main.js +144 -72
- package/dist/opencode/adapter.js +28 -9
- package/dist/opencode/controller.js +1 -1
- package/dist/store/channel_inbox.d.ts +14 -0
- package/dist/store/channel_inbox.js +21 -0
- package/dist/store/database.d.ts +10 -4
- package/dist/store/database.js +13 -3
- package/dist/store/task_journal.d.ts +32 -0
- package/dist/store/task_journal.js +70 -0
- package/dist/telegram/api.d.ts +2 -2
- package/dist/telegram/api.js +10 -28
- package/dist/telegram/gateway.d.ts +13 -27
- package/dist/telegram/gateway.js +86 -43
- package/dist/telegram/media.d.ts +2 -34
- package/dist/telegram/media.js +1 -180
- package/dist/telegram/task_preview.d.ts +13 -0
- package/dist/telegram/task_preview.js +92 -0
- package/dist/telegram/task_result.d.ts +2 -0
- package/dist/telegram/task_result.js +12 -0
- package/dist/telegram/text.d.ts +1 -1
- package/dist/telegram/text.js +2 -27
- package/dist/types.d.ts +2 -2
- package/docs/dsh.md +83 -0
- package/docs/release.md +13 -11
- package/docs/zh/architecture-review.md +106 -0
- package/docs/zh/dsh.md +83 -0
- package/docs/zh/release.md +13 -11
- package/docs/zh/telegram-experience.md +40 -0
- package/package.json +7 -5
package/dist/main.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { RuntimeSupervisor } from './core/runtime_supervisor.js';
|
|
2
3
|
import crypto from 'node:crypto';
|
|
3
4
|
import fs from 'node:fs';
|
|
4
5
|
import os from 'node:os';
|
|
@@ -349,7 +350,7 @@ async function runSendVoiceCli() {
|
|
|
349
350
|
throw new Error('Telegram voice files must be 50MB or smaller.');
|
|
350
351
|
}
|
|
351
352
|
const inferredBotId = parsed.botId ?? inferTelegramBotId(process.env.CODEX_HOME) ?? inferTelegramBotId(config.codexHome);
|
|
352
|
-
const allTokens = [...config.codexBotTokens, ...config.antigravityBotTokens];
|
|
353
|
+
const allTokens = [...config.codexBotTokens, ...config.antigravityBotTokens, ...(config.dshBotToken ? [config.dshBotToken] : [])];
|
|
353
354
|
const { botId, botToken } = resolveTelegramVoiceTarget(allTokens, inferredBotId);
|
|
354
355
|
const { BridgeStore } = await import('./store/database.js');
|
|
355
356
|
const store = new BridgeStore(config.storePath);
|
|
@@ -395,7 +396,7 @@ async function runSendMediaCli() {
|
|
|
395
396
|
}
|
|
396
397
|
const plan = planTelegramOutboundMedia(filePath);
|
|
397
398
|
const inferredBotId = parsed.botId ?? inferTelegramBotId(process.env.CODEX_HOME) ?? inferTelegramBotId(config.codexHome);
|
|
398
|
-
const allTokens = [...config.codexBotTokens, ...config.antigravityBotTokens];
|
|
399
|
+
const allTokens = [...config.codexBotTokens, ...config.antigravityBotTokens, ...(config.dshBotToken ? [config.dshBotToken] : [])];
|
|
399
400
|
const { botId, botToken } = resolveTelegramVoiceTarget(allTokens, inferredBotId);
|
|
400
401
|
const { BridgeStore } = await import('./store/database.js');
|
|
401
402
|
const store = new BridgeStore(config.storePath);
|
|
@@ -605,7 +606,7 @@ function truncateStatusLine(value, maxLength) {
|
|
|
605
606
|
return normalized.length <= maxLength ? normalized : `${normalized.slice(0, Math.max(0, maxLength - 1))}…`;
|
|
606
607
|
}
|
|
607
608
|
async function runServeCli() {
|
|
608
|
-
const [{ BridgeMessagingRouter }, { TelegramMessagingPort }, { WeixinChannelAdapter }, { WeixinMessagingPort }, { attachIlinkRuntimeFromBridgeLogger }, { loadWeixinAccount }, { Logger }, { BridgeStore }, { TelegramGateway }, { CodexAppClient }, { BridgeSessionCore }, { TelegramChannelAdapter }, { AuthCandidateMirror }, { CrossNodeAuthSync }, { OpencodeTelegramRuntime }, { UnifiedBridgeCore }, { AntigravityAppClient }, { AntigravityAuthManager, linkAntigravityAuthTokens },] = await Promise.all([
|
|
609
|
+
const [{ BridgeMessagingRouter }, { TelegramMessagingPort }, { WeixinChannelAdapter }, { WeixinMessagingPort }, { attachIlinkRuntimeFromBridgeLogger }, { loadWeixinAccount }, { Logger }, { BridgeStore }, { TelegramGateway }, { CodexAppClient }, { BridgeSessionCore }, { TelegramChannelAdapter }, { AuthCandidateMirror }, { CrossNodeAuthSync }, { OpencodeTelegramRuntime }, { DshTelegramRuntime }, { createDshBackend }, { UnifiedBridgeCore }, { AntigravityAppClient }, { AntigravityAuthManager, linkAntigravityAuthTokens },] = await Promise.all([
|
|
609
610
|
import('./channels/bridge_messaging_router.js'),
|
|
610
611
|
import('./channels/telegram/telegram_messaging_port.js'),
|
|
611
612
|
import('./channels/weixin/weixin_channel_adapter.js'),
|
|
@@ -621,7 +622,9 @@ async function runServeCli() {
|
|
|
621
622
|
import('./auth/mirror.js'),
|
|
622
623
|
import('./auth/cross_node_sync.js'),
|
|
623
624
|
import('./opencode/runtime.js'),
|
|
624
|
-
import('./
|
|
625
|
+
import('./dsh/runtime.js'),
|
|
626
|
+
import('./dsh/backend.js'),
|
|
627
|
+
import('./bridge/unified_bridge.js'),
|
|
625
628
|
import('./antigravity/client.js'),
|
|
626
629
|
import('./antigravity/auth.js'),
|
|
627
630
|
]);
|
|
@@ -631,19 +634,81 @@ async function runServeCli() {
|
|
|
631
634
|
const authNotificationAggregator = createAuthRefreshNotificationAggregator(logger);
|
|
632
635
|
attachIlinkRuntimeFromBridgeLogger(logger, config.wxIlinkRouteTag);
|
|
633
636
|
const processLock = acquireProcessLock(config.lockPath);
|
|
637
|
+
const supervisor = new RuntimeSupervisor();
|
|
638
|
+
supervisor.register('process-lock', { stop: () => processLock.release() });
|
|
634
639
|
let store = null;
|
|
635
640
|
let weixinAdapter = null;
|
|
636
641
|
let activeWeixinCore = null;
|
|
637
|
-
|
|
638
|
-
|
|
642
|
+
const activeTelegramAdapters = [];
|
|
643
|
+
const managedApps = [];
|
|
639
644
|
let activeAuthMirror = null;
|
|
640
645
|
let activeAuthSync = null;
|
|
641
646
|
let activeOpencodeRuntime = null;
|
|
647
|
+
let activeDshRuntime = null;
|
|
642
648
|
let sharedCodexApp = null;
|
|
643
649
|
let sharedAntigravityApp = null;
|
|
644
650
|
let sharedAntigravityAuth = null;
|
|
645
651
|
try {
|
|
646
652
|
store = new BridgeStore(config.storePath);
|
|
653
|
+
supervisor.register('store', { stop: () => store?.close() });
|
|
654
|
+
supervisor.register('native-apps', { stop: async () => {
|
|
655
|
+
const apps = new Set([...managedApps, ...(sharedCodexApp ? [sharedCodexApp] : [])]);
|
|
656
|
+
const results = await Promise.allSettled([...apps].map(app => app.stop({ terminateServer: true })));
|
|
657
|
+
for (const result of results)
|
|
658
|
+
if (result.status === 'rejected')
|
|
659
|
+
logger.warn('codex.app-server.stop_failed', { error: serializeError(result.reason) });
|
|
660
|
+
} });
|
|
661
|
+
supervisor.register('channels-and-sync', { stop: async () => {
|
|
662
|
+
const failures = [];
|
|
663
|
+
const cleanups = [() => authNotificationAggregator.flushAll(), () => activeAuthSync?.stop(), () => activeAuthMirror?.stop(),
|
|
664
|
+
() => weixinAdapter?.stop(), () => activeWeixinCore?.stop({ keepTransports: true }),
|
|
665
|
+
...activeTelegramAdapters.map(adapter => () => adapter.stop()),
|
|
666
|
+
() => activeOpencodeRuntime?.stop(), () => activeDshRuntime?.stop()];
|
|
667
|
+
for (const cleanup of cleanups)
|
|
668
|
+
try {
|
|
669
|
+
await cleanup();
|
|
670
|
+
}
|
|
671
|
+
catch (error) {
|
|
672
|
+
failures.push(error);
|
|
673
|
+
}
|
|
674
|
+
if (failures.length)
|
|
675
|
+
throw new AggregateError(failures, 'Runtime cleanup failed');
|
|
676
|
+
} });
|
|
677
|
+
if (config.dshBotToken) {
|
|
678
|
+
activeDshRuntime = new DshTelegramRuntime(config, store, logger);
|
|
679
|
+
await activeDshRuntime.start();
|
|
680
|
+
logger.info('dsh.bridge.started', activeDshRuntime.getRuntimeStatus());
|
|
681
|
+
}
|
|
682
|
+
if (config.opencodeBotToken) {
|
|
683
|
+
activeOpencodeRuntime = new OpencodeTelegramRuntime(config, store, logger);
|
|
684
|
+
await activeOpencodeRuntime.start();
|
|
685
|
+
logger.info('opencode.bridge.started', activeOpencodeRuntime.getRuntimeStatus());
|
|
686
|
+
}
|
|
687
|
+
if (config.dshBotToken && config.codexBotTokens.length === 0 && config.antigravityBotTokens.length === 0) {
|
|
688
|
+
const writeDshStatus = (running = true) => {
|
|
689
|
+
const status = activeDshRuntime.getRuntimeStatus();
|
|
690
|
+
writeRuntimeStatus(config.statusPath, {
|
|
691
|
+
running, connected: running && status.connected, userAgent: 'DSH ACP', botUsername: status.username,
|
|
692
|
+
currentBindings: store.countBindings(), pendingApprovals: status.pendingApprovals, pendingUserInputs: 0, queuedTurns: store.countQueuedTurnInputs(),
|
|
693
|
+
activeTurns: running ? status.activeTurns + (activeOpencodeRuntime?.getRuntimeStatus().activeTurns ?? 0) : 0, lastError: null, updatedAt: new Date().toISOString(),
|
|
694
|
+
channels: { telegram: running, weixin: false }, bots: [{ ...status, connected: running && status.connected }, ...(activeOpencodeRuntime ? [{ id: 'opencode', username: activeOpencodeRuntime.getRuntimeStatus().botUsername, connected: running && activeOpencodeRuntime.getRuntimeStatus().connected, activeTurns: activeOpencodeRuntime.getRuntimeStatus().activeTurns }] : [])],
|
|
695
|
+
});
|
|
696
|
+
};
|
|
697
|
+
writeDshStatus();
|
|
698
|
+
const timer = setInterval(writeDshStatus, 5000);
|
|
699
|
+
let shutdownPromise = null;
|
|
700
|
+
const shutdown = () => shutdownPromise ??= (async () => {
|
|
701
|
+
clearInterval(timer);
|
|
702
|
+
writeDshStatus(false);
|
|
703
|
+
const failures = await supervisor.stop();
|
|
704
|
+
for (const failure of failures)
|
|
705
|
+
logger.warn('bridge.shutdown_failed', { resource: failure.name, error: serializeError(failure.error) });
|
|
706
|
+
process.exit(0);
|
|
707
|
+
})();
|
|
708
|
+
process.on('SIGINT', () => void shutdown());
|
|
709
|
+
process.on('SIGTERM', () => void shutdown());
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
647
712
|
const selfUpdater = createSelfUpdateRuntime({
|
|
648
713
|
entryPoint,
|
|
649
714
|
nodePath: process.execPath,
|
|
@@ -658,12 +723,7 @@ async function runServeCli() {
|
|
|
658
723
|
if (config.codexCliBin) {
|
|
659
724
|
sharedCodexApp = new CodexAppClient(config.codexCliBin, config.codexAppLaunchCmd, config.codexAppAutolaunch, config.codexAppServerStatePath, config.codexAppServerLogPath, logger, config.codexHome ? { CODEX_HOME: config.codexHome } : null, codexApiProviderOverrides);
|
|
660
725
|
}
|
|
661
|
-
|
|
662
|
-
activeOpencodeRuntime = new OpencodeTelegramRuntime(config, store, logger);
|
|
663
|
-
await activeOpencodeRuntime.start();
|
|
664
|
-
logger.info('opencode.bridge.started', activeOpencodeRuntime.getRuntimeStatus());
|
|
665
|
-
}
|
|
666
|
-
const totalBots = config.codexBotTokens.length + config.antigravityBotTokens.length;
|
|
726
|
+
const totalBots = config.codexBotTokens.length + config.antigravityBotTokens.length + (config.dshBotToken ? 1 : 0);
|
|
667
727
|
const namespacedScopes = totalBots > 1;
|
|
668
728
|
const seeds = [];
|
|
669
729
|
const canonicalAuthDir = config.codexAuthDir ?? config.codexHome ?? path.join(os.homedir(), '.codex');
|
|
@@ -716,6 +776,7 @@ async function runServeCli() {
|
|
|
716
776
|
...codexApiProviderOverrides,
|
|
717
777
|
...(isCodexDefault ? [] : ['cli_auth_credentials_store="file"']),
|
|
718
778
|
]);
|
|
779
|
+
managedApps.push(codexApp);
|
|
719
780
|
// Antigravity environment
|
|
720
781
|
const isAntigravityDefault = backend === 'antigravity' && isDefaultRuntimeForBackend;
|
|
721
782
|
let antigravityHome;
|
|
@@ -787,9 +848,8 @@ async function runServeCli() {
|
|
|
787
848
|
restoreImportedAuthCandidateState(store, event.record.candidateName, authRuntimeIds);
|
|
788
849
|
},
|
|
789
850
|
});
|
|
790
|
-
await mirror.initialize();
|
|
791
851
|
activeAuthMirror = mirror;
|
|
792
|
-
|
|
852
|
+
await mirror.initialize();
|
|
793
853
|
const lastSelfUpdatePath = path.join(APP_HOME, 'runtime', 'last-self-update.json');
|
|
794
854
|
let lastSelfUpdate = readSelfUpdateStatus(lastSelfUpdatePath);
|
|
795
855
|
const runtimes = [];
|
|
@@ -809,27 +869,27 @@ async function runServeCli() {
|
|
|
809
869
|
...(first?.codexAppServer ? { codexAppServer: first.codexAppServer } : {}),
|
|
810
870
|
botUsername: first?.botUsername ?? null,
|
|
811
871
|
currentBindings: store.countBindings(),
|
|
812
|
-
pendingApprovals: store.countPendingApprovals(),
|
|
872
|
+
pendingApprovals: store.countPendingApprovals() + statuses.reduce((sum, status) => sum + status.pendingApprovals, 0) + (activeDshRuntime?.getRuntimeStatus().pendingApprovals ?? 0),
|
|
813
873
|
pendingUserInputs: store.countPendingUserInputs(),
|
|
814
874
|
queuedTurns: store.countQueuedTurnInputs(),
|
|
815
875
|
activeTurns: statuses.reduce((sum, status) => sum + status.activeTurns, 0)
|
|
816
|
-
+ (weixinStatus?.activeTurns ?? 0),
|
|
876
|
+
+ (weixinStatus?.activeTurns ?? 0) + (activeDshRuntime?.getRuntimeStatus().activeTurns ?? 0),
|
|
817
877
|
lastError: statuses.find((status) => status.lastError)?.lastError
|
|
818
878
|
?? weixinStatus?.lastError
|
|
819
879
|
?? null,
|
|
820
880
|
updatedAt: new Date().toISOString(),
|
|
821
881
|
channels: { telegram: running, weixin: running && config.wxEnabled },
|
|
822
|
-
bots: runtimes.map((runtime, index) => ({
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
882
|
+
bots: [...runtimes.map((runtime, index) => ({
|
|
883
|
+
id: runtime.id,
|
|
884
|
+
username: statuses[index]?.botUsername ?? runtime.bot.username,
|
|
885
|
+
connected: running && Boolean(statuses[index]?.connected),
|
|
886
|
+
activeTurns: running ? (statuses[index]?.activeTurns ?? 0) : 0,
|
|
887
|
+
runtimeKind: runtime.sharedDefaultRuntime ? 'default' : 'isolated',
|
|
888
|
+
defaultBackend: runtime.defaultBackend,
|
|
889
|
+
codexHome: statuses[index]?.codexHome ?? runtime.home,
|
|
890
|
+
antigravityHome: runtime.antigravityHome,
|
|
891
|
+
...(statuses[index]?.codexAppServer ? { codexAppServer: statuses[index].codexAppServer } : {}),
|
|
892
|
+
})), ...(activeDshRuntime ? [activeDshRuntime.getRuntimeStatus()] : [])],
|
|
833
893
|
...(weixinStatus ? {
|
|
834
894
|
weixinRuntime: {
|
|
835
895
|
connected: running && weixinStatus.connected,
|
|
@@ -844,6 +904,7 @@ async function runServeCli() {
|
|
|
844
904
|
});
|
|
845
905
|
};
|
|
846
906
|
const authSyncLocalIdle = () => runtimes.every((runtime) => runtime.core.isIdleForServiceUpdate())
|
|
907
|
+
&& (activeDshRuntime?.getRuntimeStatus().activeTurns ?? 0) === 0
|
|
847
908
|
&& (!activeWeixinCore || activeWeixinCore.isIdleForServiceUpdate())
|
|
848
909
|
&& mirror.isIdle();
|
|
849
910
|
const clusterUpdateScheduler = createClusterUpdateScheduler({
|
|
@@ -896,19 +957,19 @@ async function runServeCli() {
|
|
|
896
957
|
statusUpdated: () => writeAggregateStatus(),
|
|
897
958
|
getServiceStatus: async () => ({
|
|
898
959
|
currentVersion: readPackageVersion(),
|
|
899
|
-
bots: await Promise.all(runtimes.map(async (runtime) => {
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
960
|
+
bots: [...await Promise.all(runtimes.map(async (runtime) => {
|
|
961
|
+
const status = runtime.core.getRuntimeStatus();
|
|
962
|
+
return {
|
|
963
|
+
id: runtime.id,
|
|
964
|
+
username: status.botUsername ?? runtime.bot.username,
|
|
965
|
+
connected: status.connected,
|
|
966
|
+
activeTurns: status.activeTurns,
|
|
967
|
+
runtimeKind: runtime.sharedDefaultRuntime ? 'default' : 'isolated',
|
|
968
|
+
currentAuth: await runtime.core.getCurrentAuthLabel().catch(() => null),
|
|
969
|
+
codexHome: status.codexHome ?? runtime.home,
|
|
970
|
+
...(status.codexAppServer ? { codexAppServer: status.codexAppServer } : {}),
|
|
971
|
+
};
|
|
972
|
+
})), ...(activeDshRuntime ? [activeDshRuntime.getRuntimeStatus()] : [])],
|
|
912
973
|
...(activeWeixinCore ? {
|
|
913
974
|
weixinRuntime: {
|
|
914
975
|
connected: activeWeixinCore.getRuntimeStatus().connected,
|
|
@@ -940,18 +1001,20 @@ async function runServeCli() {
|
|
|
940
1001
|
antigravityApp: seed.antigravityApp,
|
|
941
1002
|
antigravityAuth: seed.antigravityAuth,
|
|
942
1003
|
defaultBackendId: seed.defaultBackend,
|
|
1004
|
+
backends: seed.config.dsh ? [createDshBackend(seed.config, store, logger, telegramMessaging)] : [],
|
|
943
1005
|
selfUpdater,
|
|
944
1006
|
});
|
|
945
|
-
|
|
1007
|
+
const telegram = new TelegramChannelAdapter(core);
|
|
1008
|
+
activeTelegramAdapters.push(telegram);
|
|
1009
|
+
runtimes.push({ ...seed, core, telegram });
|
|
946
1010
|
}
|
|
947
1011
|
if (config.wxEnabled) {
|
|
948
1012
|
const weixinApp = new CodexAppClient(config.codexCliBin, config.codexAppLaunchCmd, config.codexAppAutolaunch, config.codexAppServerStatePath, config.codexAppServerLogPath, logger, null, codexApiProviderOverrides);
|
|
1013
|
+
managedApps.push(weixinApp);
|
|
949
1014
|
const outbound = new BridgeMessagingRouter(new TelegramMessagingPort(seeds[0].bot), new WeixinMessagingPort(store, (id) => loadWeixinAccount(config.weixinAccountsDir, id)));
|
|
950
1015
|
activeWeixinCore = new BridgeSessionCore(config, store, logger, seeds[0].bot, weixinApp, outbound, selfUpdater, coordinator, false);
|
|
951
|
-
managedApps.push(weixinApp);
|
|
952
1016
|
weixinAdapter = new WeixinChannelAdapter(activeWeixinCore, store, config, logger);
|
|
953
1017
|
}
|
|
954
|
-
activeTelegramAdapters = runtimes.map((runtime) => runtime.telegram);
|
|
955
1018
|
if (config.authSyncEnabled) {
|
|
956
1019
|
const authSyncTransportBot = seeds[0];
|
|
957
1020
|
const authSyncTransportLabel = authSyncTransportBot.bot.username
|
|
@@ -1018,38 +1081,22 @@ async function runServeCli() {
|
|
|
1018
1081
|
}
|
|
1019
1082
|
writeAggregateStatus();
|
|
1020
1083
|
logger.info('bridge.started', { bots: runtimes.map((runtime) => runtime.id) });
|
|
1021
|
-
|
|
1084
|
+
let shutdownPromise = null;
|
|
1085
|
+
const shutdown = (signal) => shutdownPromise ??= (async () => {
|
|
1022
1086
|
logger.info('bridge.shutting_down', { signal });
|
|
1023
|
-
await authNotificationAggregator.flushAll();
|
|
1024
|
-
authSync?.stop();
|
|
1025
|
-
mirror.stop();
|
|
1026
|
-
await weixinAdapter?.stop();
|
|
1027
|
-
await activeWeixinCore?.stop();
|
|
1028
|
-
await Promise.all(runtimes.map((runtime) => runtime.telegram.stop()));
|
|
1029
|
-
await activeOpencodeRuntime?.stop();
|
|
1030
1087
|
writeAggregateStatus(false);
|
|
1031
|
-
const
|
|
1032
|
-
|
|
1033
|
-
logger.warn('
|
|
1034
|
-
})));
|
|
1035
|
-
store?.close();
|
|
1036
|
-
processLock.release();
|
|
1088
|
+
const failures = await supervisor.stop();
|
|
1089
|
+
for (const failure of failures)
|
|
1090
|
+
logger.warn('bridge.shutdown_failed', { resource: failure.name, error: serializeError(failure.error) });
|
|
1037
1091
|
process.exit(0);
|
|
1038
|
-
};
|
|
1092
|
+
})();
|
|
1039
1093
|
process.on('SIGINT', () => void shutdown('SIGINT'));
|
|
1040
1094
|
process.on('SIGTERM', () => void shutdown('SIGTERM'));
|
|
1041
1095
|
}
|
|
1042
1096
|
catch (error) {
|
|
1043
|
-
await
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
await weixinAdapter?.stop().catch(() => { });
|
|
1047
|
-
await activeWeixinCore?.stop().catch(() => { });
|
|
1048
|
-
await Promise.allSettled(activeTelegramAdapters.map((adapter) => adapter.stop()));
|
|
1049
|
-
await activeOpencodeRuntime?.stop().catch(() => { });
|
|
1050
|
-
await Promise.allSettled(managedApps.map((app) => app.stop({ terminateServer: true })));
|
|
1051
|
-
store?.close();
|
|
1052
|
-
processLock.release();
|
|
1097
|
+
const failures = await supervisor.stop();
|
|
1098
|
+
for (const failure of failures)
|
|
1099
|
+
logger.warn('bridge.shutdown_failed', { resource: failure.name, error: serializeError(failure.error) });
|
|
1053
1100
|
throw error;
|
|
1054
1101
|
}
|
|
1055
1102
|
}
|
|
@@ -1537,10 +1584,23 @@ async function configureEnvInteractively(envPath, existed) {
|
|
|
1537
1584
|
else {
|
|
1538
1585
|
skipped.push('TG_ALLOWED_USER_ID');
|
|
1539
1586
|
}
|
|
1587
|
+
const dshToken = sanitizeEnvInput(await rl.question('DeepSeek Harness Telegram bot token (DSH_BOT_TOKEN) [optional]: '));
|
|
1588
|
+
if (dshToken) {
|
|
1589
|
+
updates.DSH_BOT_TOKEN = dshToken;
|
|
1590
|
+
if (!codexTokens && !existed) {
|
|
1591
|
+
updates.CODEX_BOT_TOKENS = '';
|
|
1592
|
+
skipped.splice(skipped.indexOf('CODEX_BOT_TOKENS'), 1);
|
|
1593
|
+
}
|
|
1594
|
+
const dshSource = sanitizeEnvInput(await rl.question('DeepSeek Harness source directory (DSH_SOURCE_DIR) [Enter to use installed dsh]: '));
|
|
1595
|
+
if (dshSource)
|
|
1596
|
+
updates.DSH_SOURCE_DIR = normalizeUserPath(dshSource);
|
|
1597
|
+
if (!/^\d+:[A-Za-z0-9_-]+$/.test(dshToken))
|
|
1598
|
+
warnings.push('DSH_BOT_TOKEN does not look like a standard Telegram bot token.');
|
|
1599
|
+
}
|
|
1540
1600
|
const cwdDefault = defaultInitCwd();
|
|
1541
1601
|
const cwdPrompt = cwdDefault
|
|
1542
|
-
? `Default
|
|
1543
|
-
: 'Default
|
|
1602
|
+
? `Default workspace (DEFAULT_CWD) [${cwdDefault}]: `
|
|
1603
|
+
: 'Default workspace (DEFAULT_CWD): ';
|
|
1544
1604
|
const cwdAnswer = sanitizeEnvInput(await rl.question(cwdPrompt));
|
|
1545
1605
|
const cwd = cwdAnswer ? normalizeUserPath(cwdAnswer) : cwdDefault;
|
|
1546
1606
|
if (cwd) {
|
|
@@ -1741,12 +1801,24 @@ function runDoctorChecks() {
|
|
|
1741
1801
|
...(process.env.ANTIGRAVITY_BOT_TOKENS ?? '').split(','),
|
|
1742
1802
|
process.env.ANTIGRAVITY_BOT_TOKEN ?? '',
|
|
1743
1803
|
].map((value) => value.trim()).filter(Boolean);
|
|
1804
|
+
const dshToken = process.env.DSH_BOT_TOKEN?.trim();
|
|
1805
|
+
const dshFlag = process.env.DSH_ENABLED;
|
|
1806
|
+
const dshEnabled = dshFlag ? dshFlag !== 'false' && dshFlag !== '0' : Boolean(dshToken || process.env.DSH_SOURCE_DIR?.trim() || process.env.DSH_CLI_BIN?.trim());
|
|
1744
1807
|
const checks = [
|
|
1745
1808
|
['node >= 24', Number(process.versions.node.split('.')[0]) >= 24],
|
|
1746
|
-
['
|
|
1747
|
-
['telegram bot token(s) configured', codexTokens.length > 0 || antigravityTokens.length > 0],
|
|
1809
|
+
['telegram bot token(s) configured', codexTokens.length > 0 || antigravityTokens.length > 0 || Boolean(dshToken)],
|
|
1748
1810
|
['telegram allowed user configured', Boolean(process.env.TG_ALLOWED_USER_ID)],
|
|
1749
1811
|
];
|
|
1812
|
+
if (codexTokens.length > 0 || process.env.WX_ENABLED === 'true' || process.env.WX_ENABLED === '1')
|
|
1813
|
+
checks.push(['codex cli available', hasConfiguredCommand(configuredCodexBin, 'codex')]);
|
|
1814
|
+
if (dshEnabled) {
|
|
1815
|
+
const source = process.env.DSH_SOURCE_DIR?.trim();
|
|
1816
|
+
checks.push(['DSH CLI available', source ? fs.existsSync(path.join(path.resolve(source), 'apps/cli/src/bin.ts')) : hasConfiguredCommand(process.env.DSH_CLI_BIN, 'dsh')]);
|
|
1817
|
+
if (dshToken)
|
|
1818
|
+
checks.push(['DSH uses an independent Telegram bot', ![...codexTokens, ...antigravityTokens, process.env.OPENCODE_BOT_TOKEN?.trim()].includes(dshToken)]);
|
|
1819
|
+
}
|
|
1820
|
+
if (dshToken)
|
|
1821
|
+
checks.push(['DSH backend enabled for its Telegram bot', dshEnabled]);
|
|
1750
1822
|
if (process.env.OPENCODE_BOT_TOKEN?.trim()) {
|
|
1751
1823
|
const configuredOpencodeBin = process.env.OPENCODE_CLI_BIN;
|
|
1752
1824
|
checks.push(['opencode cli available', hasConfiguredCommand(configuredOpencodeBin, 'opencode')]);
|
package/dist/opencode/adapter.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
2
|
import { formatSdkError } from './client.js';
|
|
3
|
-
import { buildAttachmentPrompt } from '../
|
|
3
|
+
import { buildAttachmentPrompt } from '../core/attachment_files.js';
|
|
4
4
|
export class OpencodeEngineAdapter {
|
|
5
5
|
client;
|
|
6
6
|
defaultModel;
|
|
@@ -49,6 +49,8 @@ export class OpencodeEngineAdapter {
|
|
|
49
49
|
}
|
|
50
50
|
let finalResult = null;
|
|
51
51
|
let turnError = null;
|
|
52
|
+
let accumulatedResponse = '';
|
|
53
|
+
let cleanup = () => { };
|
|
52
54
|
const resultWaiters = [];
|
|
53
55
|
const resolveWaiters = (res) => {
|
|
54
56
|
while (resultWaiters.length > 0) {
|
|
@@ -68,8 +70,9 @@ export class OpencodeEngineAdapter {
|
|
|
68
70
|
}
|
|
69
71
|
if (cancelled)
|
|
70
72
|
return;
|
|
71
|
-
let accumulatedResponse = '';
|
|
72
73
|
const onEvent = (ev) => {
|
|
74
|
+
if (cancelled)
|
|
75
|
+
return;
|
|
73
76
|
if ('sessionId' in ev && ev.sessionId === sessionId) {
|
|
74
77
|
if (ev.kind === 'text') {
|
|
75
78
|
if (ev.delta) {
|
|
@@ -111,7 +114,7 @@ export class OpencodeEngineAdapter {
|
|
|
111
114
|
}
|
|
112
115
|
}
|
|
113
116
|
};
|
|
114
|
-
|
|
117
|
+
cleanup = () => {
|
|
115
118
|
this.client.off('event', onEvent);
|
|
116
119
|
};
|
|
117
120
|
this.client.on('event', onEvent);
|
|
@@ -138,19 +141,35 @@ export class OpencodeEngineAdapter {
|
|
|
138
141
|
}
|
|
139
142
|
}
|
|
140
143
|
catch (err) {
|
|
144
|
+
cleanup();
|
|
141
145
|
turnError = err instanceof Error ? err : new Error(String(err));
|
|
142
146
|
emitter.emit('error', turnError);
|
|
143
147
|
resolveWaiters(null);
|
|
144
148
|
}
|
|
145
149
|
};
|
|
146
|
-
|
|
150
|
+
const running = run();
|
|
151
|
+
let cancelling;
|
|
147
152
|
return {
|
|
148
|
-
turnId
|
|
153
|
+
get turnId() { return sessionId ?? undefined; },
|
|
149
154
|
cancel: () => {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
155
|
+
if (finalResult)
|
|
156
|
+
return Promise.resolve();
|
|
157
|
+
cancelling ??= (async () => {
|
|
158
|
+
cancelled = true;
|
|
159
|
+
await running;
|
|
160
|
+
if (finalResult)
|
|
161
|
+
return;
|
|
162
|
+
if (sessionId) {
|
|
163
|
+
const result = await this.client.getClient().session.abort({ sessionID: sessionId });
|
|
164
|
+
if (result.error)
|
|
165
|
+
throw new Error(formatSdkError(result.error));
|
|
166
|
+
}
|
|
167
|
+
cleanup();
|
|
168
|
+
finalResult = { kind: 'result', status: 'INTERRUPTED', response: accumulatedResponse, conversationId: sessionId };
|
|
169
|
+
emitter.emit('result', finalResult);
|
|
170
|
+
resolveWaiters(finalResult);
|
|
171
|
+
})();
|
|
172
|
+
return cancelling.catch(error => { cancelling = undefined; throw error; });
|
|
154
173
|
},
|
|
155
174
|
waitForResult: async () => {
|
|
156
175
|
if (finalResult)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import type { ChannelInbound } from '../core/channel_events.js';
|
|
3
|
+
export interface InboxEntry {
|
|
4
|
+
id: string;
|
|
5
|
+
inbound: ChannelInbound;
|
|
6
|
+
}
|
|
7
|
+
/** Durable intake precedes business classification and acknowledgement to the transport. */
|
|
8
|
+
export declare class ChannelInbox {
|
|
9
|
+
private readonly db;
|
|
10
|
+
constructor(db: DatabaseSync);
|
|
11
|
+
accept(id: string, inbound: ChannelInbound): boolean;
|
|
12
|
+
complete(id: string): void;
|
|
13
|
+
pending(): InboxEntry[];
|
|
14
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Durable intake precedes business classification and acknowledgement to the transport. */
|
|
2
|
+
export class ChannelInbox {
|
|
3
|
+
db;
|
|
4
|
+
constructor(db) {
|
|
5
|
+
this.db = db;
|
|
6
|
+
db.exec(`CREATE TABLE IF NOT EXISTS channel_inbox (
|
|
7
|
+
receipt_id TEXT PRIMARY KEY, scope_id TEXT NOT NULL, payload TEXT,
|
|
8
|
+
completed_at INTEGER, created_at INTEGER NOT NULL);
|
|
9
|
+
CREATE INDEX IF NOT EXISTS channel_inbox_pending ON channel_inbox(completed_at, created_at);`);
|
|
10
|
+
db.prepare('DELETE FROM channel_inbox WHERE completed_at < ?').run(Date.now() - 90 * 86400_000);
|
|
11
|
+
}
|
|
12
|
+
accept(id, inbound) {
|
|
13
|
+
this.db.prepare('INSERT OR IGNORE INTO channel_inbox VALUES (?, ?, ?, NULL, ?)').run(id, inbound.event.scopeId, JSON.stringify(inbound), Date.now());
|
|
14
|
+
return this.db.prepare('SELECT completed_at FROM channel_inbox WHERE receipt_id = ?').get(id)?.completed_at === null;
|
|
15
|
+
}
|
|
16
|
+
complete(id) { this.db.prepare('UPDATE channel_inbox SET payload = NULL, completed_at = ? WHERE receipt_id = ?').run(Date.now(), id); }
|
|
17
|
+
pending() {
|
|
18
|
+
return this.db.prepare('SELECT receipt_id, payload FROM channel_inbox WHERE completed_at IS NULL ORDER BY created_at, receipt_id').all()
|
|
19
|
+
.map(row => ({ id: String(row.receipt_id), inbound: JSON.parse(String(row.payload)) }));
|
|
20
|
+
}
|
|
21
|
+
}
|
package/dist/store/database.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ChannelInbox } from './channel_inbox.js';
|
|
2
|
+
import { TaskJournal } from './task_journal.js';
|
|
3
|
+
import type { AccessPresetValue, ActiveTurnMessageMode, AppLocale, CachedThread, ChatSessionSettings, CollaborationModeValue, GuidedPlanSessionRecord, GuidedPlanSessionState, PendingAttachmentBatchRecord, PendingAttachmentBatchStatus, PendingApprovalRecord, QueuedTurnInputRecord, QueuedTurnInputStatus, ThreadBinding } from '../types.js';
|
|
2
4
|
import type { CumulativeTokenTotals, CumulativeTokenUsageRecord, TokenUsageDelta } from './token_usage.js';
|
|
3
5
|
export interface ActiveTurnPreviewRecord {
|
|
4
6
|
turnId: string;
|
|
@@ -50,6 +52,8 @@ export interface CodexAuthPoolStats {
|
|
|
50
52
|
export type CodexAuthCandidateState = 'active' | 'needs_repair';
|
|
51
53
|
export declare class BridgeStore {
|
|
52
54
|
private db;
|
|
55
|
+
readonly taskJournal: TaskJournal;
|
|
56
|
+
readonly channelInbox: ChannelInbox;
|
|
53
57
|
constructor(dbPath: string);
|
|
54
58
|
getTelegramOffset(botKey: string): number;
|
|
55
59
|
setTelegramOffset(botKey: string, updateId: number): void;
|
|
@@ -71,9 +75,11 @@ export declare class BridgeStore {
|
|
|
71
75
|
threadId: string;
|
|
72
76
|
}>;
|
|
73
77
|
getChatSettings(chatId: string): ChatSessionSettings | null;
|
|
74
|
-
setChatSettings(chatId: string, model: string | null, reasoningEffort:
|
|
78
|
+
setChatSettings(chatId: string, model: string | null, reasoningEffort: string | null, locale?: AppLocale | null): void;
|
|
75
79
|
setChatModel(chatId: string, model: string | null): void;
|
|
76
|
-
setChatEffort(chatId: string, effort:
|
|
80
|
+
setChatEffort(chatId: string, effort: string | null): void;
|
|
81
|
+
/** Persist an adapter-advertised reasoning id without imposing Codex's vocabulary. */
|
|
82
|
+
setChatEngineEffort(chatId: string, effort: string | null): void;
|
|
77
83
|
setChatLocale(chatId: string, locale: AppLocale): void;
|
|
78
84
|
setChatAccessPreset(chatId: string, accessPreset: AccessPresetValue | null): void;
|
|
79
85
|
setChatCollaborationMode(chatId: string, collaborationMode: CollaborationModeValue | null): void;
|
|
@@ -140,7 +146,7 @@ export declare class BridgeStore {
|
|
|
140
146
|
countQueuedTurnInputs(scopeId?: string): number;
|
|
141
147
|
updateQueuedTurnInputStatus(queueId: string, status: QueuedTurnInputStatus, error?: string | null): void;
|
|
142
148
|
cancelQueuedTurnInputs(scopeId: string): number;
|
|
143
|
-
requeueInterruptedQueuedTurnInputs(): number;
|
|
149
|
+
requeueInterruptedQueuedTurnInputs(scopeId?: string): number;
|
|
144
150
|
savePendingAttachmentBatch(record: PendingAttachmentBatchRecord): void;
|
|
145
151
|
getPendingAttachmentBatch(batchId: string): PendingAttachmentBatchRecord | null;
|
|
146
152
|
findPendingAttachmentBatchByMediaGroup(scopeId: string, mediaGroupId: string): PendingAttachmentBatchRecord | null;
|
package/dist/store/database.js
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import { DatabaseSync } from 'node:sqlite';
|
|
4
|
+
import { ChannelInbox } from './channel_inbox.js';
|
|
5
|
+
import { TaskJournal } from './task_journal.js';
|
|
4
6
|
import { migrateLegacyBridgeScopeIds } from './migrate_bridge_scope.js';
|
|
5
7
|
export class BridgeStore {
|
|
6
8
|
db;
|
|
9
|
+
taskJournal;
|
|
10
|
+
channelInbox;
|
|
7
11
|
constructor(dbPath) {
|
|
8
12
|
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
9
13
|
this.db = new DatabaseSync(dbPath);
|
|
14
|
+
this.taskJournal = new TaskJournal(this.db);
|
|
15
|
+
this.channelInbox = new ChannelInbox(this.db);
|
|
10
16
|
this.db.exec(`
|
|
11
17
|
CREATE TABLE IF NOT EXISTS telegram_offsets (
|
|
12
18
|
bot_key TEXT PRIMARY KEY,
|
|
@@ -403,6 +409,10 @@ export class BridgeStore {
|
|
|
403
409
|
const current = this.getChatSettings(chatId);
|
|
404
410
|
this.setChatSettings(chatId, current?.model ?? null, effort);
|
|
405
411
|
}
|
|
412
|
+
/** Persist an adapter-advertised reasoning id without imposing Codex's vocabulary. */
|
|
413
|
+
setChatEngineEffort(chatId, effort) {
|
|
414
|
+
this.setChatEffort(chatId, effort);
|
|
415
|
+
}
|
|
406
416
|
setChatLocale(chatId, locale) {
|
|
407
417
|
const current = this.getChatSettings(chatId);
|
|
408
418
|
this.writeChatSettings(chatId, current?.model ?? null, current?.reasoningEffort ?? null, locale, current?.accessPreset ?? null, current?.collaborationMode ?? null, current?.serviceTier ?? null, current?.activeTurnMessageMode ?? null, current?.activeBackendId ?? null);
|
|
@@ -748,12 +758,12 @@ export class BridgeStore {
|
|
|
748
758
|
`).run(Date.now(), Date.now(), scopeId);
|
|
749
759
|
return Number(result.changes ?? 0);
|
|
750
760
|
}
|
|
751
|
-
requeueInterruptedQueuedTurnInputs() {
|
|
761
|
+
requeueInterruptedQueuedTurnInputs(scopeId) {
|
|
752
762
|
const result = this.db.prepare(`
|
|
753
763
|
UPDATE queued_turn_inputs
|
|
754
764
|
SET status = 'queued', error = NULL, updated_at = ?, resolved_at = NULL
|
|
755
|
-
WHERE status = 'processing'
|
|
756
|
-
`).run(Date.now());
|
|
765
|
+
WHERE status = 'processing'${scopeId ? ' AND scope_id = ?' : ''}
|
|
766
|
+
`).run(Date.now(), ...(scopeId ? [scopeId] : []));
|
|
757
767
|
return Number(result.changes ?? 0);
|
|
758
768
|
}
|
|
759
769
|
savePendingAttachmentBatch(record) {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import type { ChannelTextEvent } from '../core/channel_events.js';
|
|
3
|
+
import type { EngineTurnRequest, EngineTurnResult } from '../core/engine_spi.js';
|
|
4
|
+
export type TaskState = 'accepted' | 'queued' | 'running' | 'awaiting_confirmation' | 'delivery_pending' | 'completed' | 'failed' | 'cancelled';
|
|
5
|
+
export interface JournalTask {
|
|
6
|
+
id: string;
|
|
7
|
+
backendId: string;
|
|
8
|
+
state: TaskState;
|
|
9
|
+
event: ChannelTextEvent;
|
|
10
|
+
sourcePrompt: string;
|
|
11
|
+
request: EngineTurnRequest;
|
|
12
|
+
queueId: string | null;
|
|
13
|
+
previewMessageId: number;
|
|
14
|
+
result: EngineTurnResult | null;
|
|
15
|
+
delivery: string[];
|
|
16
|
+
deliveredChunks: number;
|
|
17
|
+
error: string | null;
|
|
18
|
+
createdAt: number;
|
|
19
|
+
updatedAt: number;
|
|
20
|
+
}
|
|
21
|
+
/** Original inputs, native outcome and delivery progress are distinct durable facts. */
|
|
22
|
+
export declare class TaskJournal {
|
|
23
|
+
private readonly db;
|
|
24
|
+
constructor(db: DatabaseSync);
|
|
25
|
+
atomic<T>(operation: () => T): T;
|
|
26
|
+
insert(task: JournalTask): void;
|
|
27
|
+
get(id: string): JournalTask | null;
|
|
28
|
+
listUnfinished(scopeId?: string): JournalTask[];
|
|
29
|
+
findReceipt(event: ChannelTextEvent, prompt: string): JournalTask | null;
|
|
30
|
+
forQueue(queueId: string): JournalTask | null;
|
|
31
|
+
update(id: string, state: TaskState, patch?: Partial<Omit<JournalTask, 'id' | 'backendId' | 'createdAt' | 'state'>>): JournalTask;
|
|
32
|
+
}
|