@0xmaxma/claude-gateway 2.0.4 → 2.0.6
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/README.md +10 -1
- package/dist/agent/builtin-commands.d.ts +1 -1
- package/dist/agent/builtin-commands.d.ts.map +1 -1
- package/dist/agent/builtin-commands.js +3 -2
- package/dist/agent/builtin-commands.js.map +1 -1
- package/dist/agent/runner.d.ts +123 -9
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +215 -182
- package/dist/agent/runner.js.map +1 -1
- package/dist/agent/session-compaction-scheduler.d.ts +22 -0
- package/dist/agent/session-compaction-scheduler.d.ts.map +1 -0
- package/dist/agent/session-compaction-scheduler.js +40 -0
- package/dist/agent/session-compaction-scheduler.js.map +1 -0
- package/dist/agent/skill-learning/index.d.ts.map +1 -1
- package/dist/agent/skill-learning/index.js +27 -8
- package/dist/agent/skill-learning/index.js.map +1 -1
- package/dist/agent/skill-learning/reviewer.d.ts +3 -1
- package/dist/agent/skill-learning/reviewer.d.ts.map +1 -1
- package/dist/agent/skill-learning/reviewer.js +6 -2
- package/dist/agent/skill-learning/reviewer.js.map +1 -1
- package/dist/agent/turn-stream.d.ts +3 -1
- package/dist/agent/turn-stream.d.ts.map +1 -1
- package/dist/agent/turn-stream.js +8 -4
- package/dist/agent/turn-stream.js.map +1 -1
- package/dist/api/gateway-router.d.ts +2 -0
- package/dist/api/gateway-router.d.ts.map +1 -1
- package/dist/api/gateway-router.js +323 -16
- package/dist/api/gateway-router.js.map +1 -1
- package/dist/api/memory-activity.d.ts +18 -0
- package/dist/api/memory-activity.d.ts.map +1 -0
- package/dist/api/memory-activity.js +97 -0
- package/dist/api/memory-activity.js.map +1 -0
- package/dist/api/router.js +11 -11
- package/dist/api/router.js.map +1 -1
- package/dist/api/slack-webhook-router.js +1 -1
- package/dist/api/slack-webhook-router.js.map +1 -1
- package/dist/api/voice-router.d.ts +1 -0
- package/dist/api/voice-router.d.ts.map +1 -1
- package/dist/api/voice-router.js +129 -7
- package/dist/api/voice-router.js.map +1 -1
- package/dist/config/claude-settings.d.ts +14 -0
- package/dist/config/claude-settings.d.ts.map +1 -1
- package/dist/config/claude-settings.js +20 -0
- package/dist/config/claude-settings.js.map +1 -1
- package/dist/orchestration/agent-cli-session.d.ts +46 -0
- package/dist/orchestration/agent-cli-session.d.ts.map +1 -0
- package/dist/orchestration/agent-cli-session.js +121 -0
- package/dist/orchestration/agent-cli-session.js.map +1 -0
- package/dist/orchestration/agent-tool-schemas.d.ts +6 -0
- package/dist/orchestration/agent-tool-schemas.d.ts.map +1 -0
- package/dist/orchestration/agent-tool-schemas.js +23 -0
- package/dist/orchestration/agent-tool-schemas.js.map +1 -0
- package/dist/orchestration/bridge.d.ts +6 -3
- package/dist/orchestration/bridge.d.ts.map +1 -1
- package/dist/orchestration/bridge.js +31 -14
- package/dist/orchestration/bridge.js.map +1 -1
- package/dist/orchestration/browser-voice.d.ts +14 -0
- package/dist/orchestration/browser-voice.d.ts.map +1 -0
- package/dist/orchestration/browser-voice.js +30 -0
- package/dist/orchestration/browser-voice.js.map +1 -0
- package/dist/orchestration/compact-measurements.d.ts +21 -0
- package/dist/orchestration/compact-measurements.d.ts.map +1 -0
- package/dist/orchestration/compact-measurements.js +62 -0
- package/dist/orchestration/compact-measurements.js.map +1 -0
- package/dist/orchestration/config.d.ts +4 -0
- package/dist/orchestration/config.d.ts.map +1 -1
- package/dist/orchestration/config.js +1 -1
- package/dist/orchestration/config.js.map +1 -1
- package/dist/orchestration/context-delivery.d.ts +32 -0
- package/dist/orchestration/context-delivery.d.ts.map +1 -0
- package/dist/orchestration/context-delivery.js +101 -0
- package/dist/orchestration/context-delivery.js.map +1 -0
- package/dist/orchestration/context-footprint.d.ts +14 -0
- package/dist/orchestration/context-footprint.d.ts.map +1 -0
- package/dist/orchestration/context-footprint.js +57 -0
- package/dist/orchestration/context-footprint.js.map +1 -0
- package/dist/orchestration/conversation-intake.d.ts +10 -6
- package/dist/orchestration/conversation-intake.d.ts.map +1 -1
- package/dist/orchestration/conversation-intake.js +22 -8
- package/dist/orchestration/conversation-intake.js.map +1 -1
- package/dist/orchestration/dashboard-charts.d.ts +32 -0
- package/dist/orchestration/dashboard-charts.d.ts.map +1 -0
- package/dist/orchestration/dashboard-charts.js +77 -0
- package/dist/orchestration/dashboard-charts.js.map +1 -0
- package/dist/orchestration/dashboard-reader-worker.d.ts +2 -0
- package/dist/orchestration/dashboard-reader-worker.d.ts.map +1 -0
- package/dist/orchestration/dashboard-reader-worker.js +173 -0
- package/dist/orchestration/dashboard-reader-worker.js.map +1 -0
- package/dist/orchestration/dashboard-reader.d.ts +15 -0
- package/dist/orchestration/dashboard-reader.d.ts.map +1 -0
- package/dist/orchestration/dashboard-reader.js +87 -0
- package/dist/orchestration/dashboard-reader.js.map +1 -0
- package/dist/orchestration/decision-context.d.ts +15 -0
- package/dist/orchestration/decision-context.d.ts.map +1 -0
- package/dist/orchestration/decision-context.js +57 -0
- package/dist/orchestration/decision-context.js.map +1 -0
- package/dist/orchestration/display-stream.d.ts +21 -2
- package/dist/orchestration/display-stream.d.ts.map +1 -1
- package/dist/orchestration/display-stream.js +61 -8
- package/dist/orchestration/display-stream.js.map +1 -1
- package/dist/orchestration/inference-errors.d.ts.map +1 -1
- package/dist/orchestration/inference-errors.js +19 -21
- package/dist/orchestration/inference-errors.js.map +1 -1
- package/dist/orchestration/mutation-recovery.d.ts +13 -0
- package/dist/orchestration/mutation-recovery.d.ts.map +1 -0
- package/dist/orchestration/mutation-recovery.js +49 -0
- package/dist/orchestration/mutation-recovery.js.map +1 -0
- package/dist/orchestration/native-compact.d.ts +8 -0
- package/dist/orchestration/native-compact.d.ts.map +1 -0
- package/dist/orchestration/native-compact.js +33 -0
- package/dist/orchestration/native-compact.js.map +1 -0
- package/dist/orchestration/notification-mailbox.d.ts +3 -1
- package/dist/orchestration/notification-mailbox.d.ts.map +1 -1
- package/dist/orchestration/notification-mailbox.js +4 -2
- package/dist/orchestration/notification-mailbox.js.map +1 -1
- package/dist/orchestration/process-turn.d.ts +16 -2
- package/dist/orchestration/process-turn.d.ts.map +1 -1
- package/dist/orchestration/process-turn.js +78 -14
- package/dist/orchestration/process-turn.js.map +1 -1
- package/dist/orchestration/progress-review.d.ts +8 -18
- package/dist/orchestration/progress-review.d.ts.map +1 -1
- package/dist/orchestration/progress-review.js +12 -14
- package/dist/orchestration/progress-review.js.map +1 -1
- package/dist/orchestration/provider-message.d.ts +7 -0
- package/dist/orchestration/provider-message.d.ts.map +1 -0
- package/dist/orchestration/provider-message.js +63 -0
- package/dist/orchestration/provider-message.js.map +1 -0
- package/dist/orchestration/response-errors.d.ts +6 -0
- package/dist/orchestration/response-errors.d.ts.map +1 -0
- package/dist/orchestration/response-errors.js +88 -0
- package/dist/orchestration/response-errors.js.map +1 -0
- package/dist/orchestration/response-schema.d.ts +33 -0
- package/dist/orchestration/response-schema.d.ts.map +1 -0
- package/dist/orchestration/response-schema.js +61 -0
- package/dist/orchestration/response-schema.js.map +1 -0
- package/dist/orchestration/runtime.d.ts +79 -0
- package/dist/orchestration/runtime.d.ts.map +1 -1
- package/dist/orchestration/runtime.js +415 -68
- package/dist/orchestration/runtime.js.map +1 -1
- package/dist/orchestration/session-compaction.d.ts +59 -0
- package/dist/orchestration/session-compaction.d.ts.map +1 -0
- package/dist/orchestration/session-compaction.js +212 -0
- package/dist/orchestration/session-compaction.js.map +1 -0
- package/dist/orchestration/skills.d.ts.map +1 -1
- package/dist/orchestration/skills.js +3 -2
- package/dist/orchestration/skills.js.map +1 -1
- package/dist/orchestration/source-policy.d.ts +1 -1
- package/dist/orchestration/source-policy.d.ts.map +1 -1
- package/dist/orchestration/source-policy.js +1 -1
- package/dist/orchestration/speech.d.ts +15 -17
- package/dist/orchestration/speech.d.ts.map +1 -1
- package/dist/orchestration/speech.js +90 -30
- package/dist/orchestration/speech.js.map +1 -1
- package/dist/orchestration/store.d.ts +2 -0
- package/dist/orchestration/store.d.ts.map +1 -1
- package/dist/orchestration/store.js +16 -1
- package/dist/orchestration/store.js.map +1 -1
- package/dist/orchestration/tasks/driver.d.ts.map +1 -1
- package/dist/orchestration/tasks/driver.js +8 -3
- package/dist/orchestration/tasks/driver.js.map +1 -1
- package/dist/orchestration/tasks/pool.d.ts.map +1 -1
- package/dist/orchestration/tasks/pool.js +8 -4
- package/dist/orchestration/tasks/pool.js.map +1 -1
- package/dist/orchestration/tasks/service.d.ts +48 -1
- package/dist/orchestration/tasks/service.d.ts.map +1 -1
- package/dist/orchestration/tasks/service.js +41 -17
- package/dist/orchestration/tasks/service.js.map +1 -1
- package/dist/orchestration/token-ledger.d.ts +132 -0
- package/dist/orchestration/token-ledger.d.ts.map +1 -0
- package/dist/orchestration/token-ledger.js +233 -0
- package/dist/orchestration/token-ledger.js.map +1 -0
- package/dist/orchestration/token-usage.d.ts +46 -0
- package/dist/orchestration/token-usage.d.ts.map +1 -0
- package/dist/orchestration/token-usage.js +102 -0
- package/dist/orchestration/token-usage.js.map +1 -0
- package/dist/orchestration/tool-activity.d.ts.map +1 -1
- package/dist/orchestration/tool-activity.js +5 -3
- package/dist/orchestration/tool-activity.js.map +1 -1
- package/dist/orchestration/tool-name.d.ts +10 -0
- package/dist/orchestration/tool-name.d.ts.map +1 -0
- package/dist/orchestration/tool-name.js +14 -0
- package/dist/orchestration/tool-name.js.map +1 -0
- package/dist/orchestration/transcript-checkpoint.d.ts +14 -0
- package/dist/orchestration/transcript-checkpoint.d.ts.map +1 -0
- package/dist/orchestration/transcript-checkpoint.js +83 -0
- package/dist/orchestration/transcript-checkpoint.js.map +1 -0
- package/dist/orchestration/unanswered-inputs.d.ts +6 -0
- package/dist/orchestration/unanswered-inputs.d.ts.map +1 -0
- package/dist/orchestration/unanswered-inputs.js +18 -0
- package/dist/orchestration/unanswered-inputs.js.map +1 -0
- package/dist/session/process.d.ts +10 -0
- package/dist/session/process.d.ts.map +1 -1
- package/dist/session/process.js +91 -9
- package/dist/session/process.js.map +1 -1
- package/dist/session/request-tool-capture.d.ts +31 -0
- package/dist/session/request-tool-capture.d.ts.map +1 -0
- package/dist/session/request-tool-capture.js +194 -0
- package/dist/session/request-tool-capture.js.map +1 -0
- package/dist/session/runtime-profile.d.ts +7 -3
- package/dist/session/runtime-profile.d.ts.map +1 -1
- package/dist/session/runtime-profile.js +3 -1
- package/dist/session/runtime-profile.js.map +1 -1
- package/dist/session/store.d.ts +7 -0
- package/dist/session/store.d.ts.map +1 -1
- package/dist/session/store.js +27 -0
- package/dist/session/store.js.map +1 -1
- package/dist/shell/tailer.d.ts +7 -5
- package/dist/shell/tailer.d.ts.map +1 -1
- package/dist/shell/tailer.js +6 -17
- package/dist/shell/tailer.js.map +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/ui/cli-viewer-ui.js +1 -1
- package/dist/ui/dashboard-charts.d.ts +3 -0
- package/dist/ui/dashboard-charts.d.ts.map +1 -0
- package/dist/ui/dashboard-charts.js +130 -0
- package/dist/ui/dashboard-charts.js.map +1 -0
- package/dist/ui/dashboard-client.d.ts +3 -0
- package/dist/ui/dashboard-client.d.ts.map +1 -0
- package/dist/ui/dashboard-client.js +184 -0
- package/dist/ui/dashboard-client.js.map +1 -0
- package/dist/ui/dashboard-controls.d.ts +4 -0
- package/dist/ui/dashboard-controls.d.ts.map +1 -0
- package/dist/ui/dashboard-controls.js +27 -0
- package/dist/ui/dashboard-controls.js.map +1 -0
- package/dist/ui/dashboard-presentation.d.ts +12 -0
- package/dist/ui/dashboard-presentation.d.ts.map +1 -0
- package/dist/ui/dashboard-presentation.js +62 -0
- package/dist/ui/dashboard-presentation.js.map +1 -0
- package/dist/ui/dashboard-range.d.ts +5 -0
- package/dist/ui/dashboard-range.d.ts.map +1 -0
- package/dist/ui/dashboard-range.js +36 -0
- package/dist/ui/dashboard-range.js.map +1 -0
- package/dist/ui/dashboard-theme.d.ts +6 -0
- package/dist/ui/dashboard-theme.d.ts.map +1 -0
- package/dist/ui/dashboard-theme.js +562 -0
- package/dist/ui/dashboard-theme.js.map +1 -0
- package/dist/ui/memory-activity.d.ts +4 -0
- package/dist/ui/memory-activity.d.ts.map +1 -0
- package/dist/ui/memory-activity.js +115 -0
- package/dist/ui/memory-activity.js.map +1 -0
- package/dist/ui/token-report-client.d.ts +3 -0
- package/dist/ui/token-report-client.d.ts.map +1 -0
- package/dist/ui/token-report-client.js +34 -0
- package/dist/ui/token-report-client.js.map +1 -0
- package/dist/ui/token-report.d.ts +90 -0
- package/dist/ui/token-report.d.ts.map +1 -0
- package/dist/ui/token-report.js +203 -0
- package/dist/ui/token-report.js.map +1 -0
- package/dist/ui/web-ui.d.ts +1 -1
- package/dist/ui/web-ui.d.ts.map +1 -1
- package/dist/ui/web-ui.js +129 -409
- package/dist/ui/web-ui.js.map +1 -1
- package/dist/voice/replay.d.ts +8 -0
- package/dist/voice/replay.d.ts.map +1 -0
- package/dist/voice/replay.js +48 -0
- package/dist/voice/replay.js.map +1 -0
- package/dist/voice/session.d.ts +9 -0
- package/dist/voice/session.d.ts.map +1 -1
- package/dist/voice/session.js +72 -14
- package/dist/voice/session.js.map +1 -1
- package/mcp/lazy-connector.test.ts +58 -0
- package/mcp/lazy-connector.ts +67 -0
- package/mcp/lazy-tools.test.ts +152 -0
- package/mcp/lazy-tools.ts +41 -0
- package/mcp/server.ts +53 -34
- package/mcp/tools/discord/commands.ts +2 -0
- package/mcp/tools/tasks/module.ts +1 -12
- package/mcp/tools/telegram/receiver-server.ts +45 -31
- package/mcp/tools/telegram/session-browser.ts +10 -0
- package/package.json +2 -1
- package/resource/dashboard/OFL.txt +93 -0
- package/resource/dashboard/poppins-300.woff2 +0 -0
- package/resource/dashboard/poppins-400.woff2 +0 -0
- package/resource/dashboard/poppins-500.woff2 +0 -0
- package/resource/dashboard/poppins-600.woff2 +0 -0
|
@@ -38,6 +38,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.GatewayRouter = void 0;
|
|
40
40
|
exports.resolveBindHost = resolveBindHost;
|
|
41
|
+
const memory_activity_1 = require("./memory-activity");
|
|
42
|
+
const dashboard_range_1 = require("../ui/dashboard-range");
|
|
43
|
+
const dashboard_reader_1 = require("../orchestration/dashboard-reader");
|
|
41
44
|
const express_1 = __importDefault(require("express"));
|
|
42
45
|
const http = __importStar(require("node:http"));
|
|
43
46
|
const child_process_1 = require("child_process");
|
|
@@ -51,6 +54,7 @@ const pty_stream_registry_1 = require("../shell/pty-stream-registry");
|
|
|
51
54
|
const control_channel_1 = require("../shell/control-channel");
|
|
52
55
|
const factory_1 = require("../watch/factory");
|
|
53
56
|
const web_ui_1 = require("../ui/web-ui");
|
|
57
|
+
const token_report_1 = require("../ui/token-report");
|
|
54
58
|
const knowledge_1 = require("../agent/knowledge");
|
|
55
59
|
const logger_1 = require("../logger");
|
|
56
60
|
const report_1 = require("../agent/dreaming/report");
|
|
@@ -228,6 +232,8 @@ class RateLimiter {
|
|
|
228
232
|
}
|
|
229
233
|
class GatewayRouter {
|
|
230
234
|
constructor(agents, configs, schedulers, gatewayConfig, cronManager, configPath, appsRegistry, appInstaller, appRegistryClient) {
|
|
235
|
+
this.dashboardReader = new dashboard_reader_1.DashboardReader();
|
|
236
|
+
this.dashboardStreams = new Set();
|
|
231
237
|
// ─── App proxy ──────────────────────────────────────────────────────────
|
|
232
238
|
/** "appName:portName" → ProxyRoute */
|
|
233
239
|
this.routeMap = new Map();
|
|
@@ -774,6 +780,19 @@ class GatewayRouter {
|
|
|
774
780
|
this.app.get('/health', (_req, res) => {
|
|
775
781
|
res.json({ status: 'ok' });
|
|
776
782
|
});
|
|
783
|
+
// Public static typeface assets contain no gateway data.
|
|
784
|
+
this.app.get('/dashboard/fonts.css', (_req, res) => {
|
|
785
|
+
res.setHeader('Cache-Control', 'public, max-age=86400');
|
|
786
|
+
res.type('css').send([300, 400, 500, 600].map(weight => `@font-face{font-family:Poppins;font-style:normal;font-weight:${weight};font-display:swap;src:url(fonts/poppins-${weight}.woff2) format('woff2')}`).join('\n'));
|
|
787
|
+
});
|
|
788
|
+
this.app.get('/dashboard/fonts/:font', (req, res) => {
|
|
789
|
+
if (!/^poppins-(300|400|500|600)\.woff2$/.test(req.params.font)) {
|
|
790
|
+
res.sendStatus(404);
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
res.setHeader('Cache-Control', 'public, max-age=86400');
|
|
794
|
+
res.sendFile(path.resolve(__dirname, '../../resource/dashboard', req.params.font));
|
|
795
|
+
});
|
|
777
796
|
// Web dashboard. When API keys are configured, require a live dashboard
|
|
778
797
|
// session cookie; otherwise serve the login page (API-key form). Keyless
|
|
779
798
|
// installs have no credential to check, so the dashboard stays open there —
|
|
@@ -797,10 +816,123 @@ class GatewayRouter {
|
|
|
797
816
|
delete require.cache[webUiPath];
|
|
798
817
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
799
818
|
const { generateDashboardHtml: fresh } = require('../ui/web-ui');
|
|
800
|
-
res.send(fresh());
|
|
819
|
+
res.send(fresh(this.gatewayConfig?.gateway?.timezone));
|
|
801
820
|
}
|
|
802
821
|
else {
|
|
803
|
-
res.send((0, web_ui_1.generateDashboardHtml)());
|
|
822
|
+
res.send((0, web_ui_1.generateDashboardHtml)(this.gatewayConfig?.gateway?.timezone));
|
|
823
|
+
}
|
|
824
|
+
});
|
|
825
|
+
// Reports expose cross-agent usage/tool inventories: the same admin gate as status.
|
|
826
|
+
for (const reportPath of ['/dashboard/token-report', '/token-report']) {
|
|
827
|
+
this.app.get(reportPath, async (req, res) => {
|
|
828
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
829
|
+
// The HTML page requires an explicit dashboard login, just like /dashboard.
|
|
830
|
+
// Reverse proxies may inject an admin API key: that must not log a browser in.
|
|
831
|
+
// The JSON /token-report endpoint retains API-key access for API clients.
|
|
832
|
+
if (reportPath === '/dashboard/token-report' && this.apiKeys.length > 0 && !this.hasValidDashSession(req)) {
|
|
833
|
+
// Relative redirect preserves deployment prefixes (e.g. /gateway) without
|
|
834
|
+
// trusting forwarded headers. Also handle Express's optional trailing slash.
|
|
835
|
+
const query = new URLSearchParams();
|
|
836
|
+
for (const key of ['agentId', 'sessionId', 'scope', 'offset'])
|
|
837
|
+
if (typeof req.query[key] === 'string')
|
|
838
|
+
query.set(key, req.query[key]);
|
|
839
|
+
res.redirect(303, (req.path.endsWith('/') ? '../' : './') + '?returnTo=' + encodeURIComponent('token-report?' + query.toString()));
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
if (!this.requireDashOrApiKey(req, res))
|
|
843
|
+
return;
|
|
844
|
+
const { agentId, sessionId } = req.query;
|
|
845
|
+
const offset = Number(req.query.offset ?? 0);
|
|
846
|
+
if (!Number.isSafeInteger(offset) || offset < 0 || offset > 1000000) {
|
|
847
|
+
res.status(400).json({ error: 'Invalid offset' });
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
if (typeof agentId !== 'string' || typeof sessionId !== 'string' || !sessionId || sessionId.length > 256) {
|
|
851
|
+
res.status(400).json({ error: 'agentId and sessionId are required' });
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
const runner = this.agents.get(agentId);
|
|
855
|
+
if (!runner) {
|
|
856
|
+
res.status(404).json({ error: 'Unknown agent' });
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
try {
|
|
860
|
+
const source = runner.getDashboardSource?.();
|
|
861
|
+
let report = source ? await this.dashboardReader.read('report', source.filename, { workspace: source.workspace, sessionId, offset, since: (0, dashboard_range_1.dashboardSince)(req.query.scope ?? (reportPath === '/token-report' ? 'all' : '24h'), Date.now(), this.gatewayConfig?.gateway?.timezone), historyFilename: source.historyFilename }) : await runner.getTokenReport(sessionId);
|
|
862
|
+
if (!report) {
|
|
863
|
+
res.status(404).json({ error: 'No recorded session token report' });
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
if (runner.isSessionCompacting?.(sessionId))
|
|
867
|
+
report = { ...report, activityStatus: 'compacting' };
|
|
868
|
+
if (report.contextWindow)
|
|
869
|
+
report.contextWindow.total = report.contextWindow.model ? await runner.dashboardContextWindow?.(report.contextWindow.model) ?? null : null;
|
|
870
|
+
if (reportPath === '/token-report')
|
|
871
|
+
res.json(report);
|
|
872
|
+
else
|
|
873
|
+
res.type('html').send((0, token_report_1.generateTokenReportHtml)(agentId, { ...report, timezone: this.gatewayConfig?.gateway?.timezone, scope: (0, dashboard_range_1.dashboardRange)(req.query.scope), sessionStatus: runner.agentSessionLiveStatus?.(sessionId) ?? 'stopped' }));
|
|
874
|
+
}
|
|
875
|
+
catch {
|
|
876
|
+
res.status(500).json({ error: 'Unable to load session token report' });
|
|
877
|
+
}
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
this.app.get('/dashboard/session', async (req, res) => {
|
|
881
|
+
if (!this.requireDashOrApiKey(req, res))
|
|
882
|
+
return;
|
|
883
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
884
|
+
const { agentId, sessionId } = req.query, offset = Number(req.query.offset ?? 0);
|
|
885
|
+
if ([agentId, sessionId].some(v => typeof v !== 'string' || !v || v.length > 256) || !Number.isSafeInteger(offset) || offset < 0 || offset > 1000000) {
|
|
886
|
+
res.status(400).json({ error: 'Invalid session query' });
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
const detailRunner = this.agents.get(String(agentId));
|
|
890
|
+
const source = detailRunner?.getDashboardSource?.();
|
|
891
|
+
if (!source) {
|
|
892
|
+
res.status(404).json({ error: 'Unknown agent' });
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
try {
|
|
896
|
+
const detail = await this.dashboardReader.read('session', source.filename, { sessionId, offset, since: (0, dashboard_range_1.dashboardSince)(req.query.scope, Date.now(), this.gatewayConfig?.gateway?.timezone), historyFilename: source.historyFilename });
|
|
897
|
+
if (!detail) {
|
|
898
|
+
res.status(404).json({ error: 'Session not found' });
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
if (detailRunner?.isSessionCompacting?.(String(sessionId)))
|
|
902
|
+
detail.activityStatus = 'compacting';
|
|
903
|
+
if (detail.contextWindow)
|
|
904
|
+
detail.contextWindow.total = detail.contextWindow.model ? await detailRunner?.dashboardContextWindow?.(detail.contextWindow.model) ?? null : null;
|
|
905
|
+
res.json({ ...detail, sessionStatus: detailRunner?.agentSessionLiveStatus?.(String(sessionId)) ?? 'stopped' });
|
|
906
|
+
}
|
|
907
|
+
catch {
|
|
908
|
+
res.status(503).json({ error: 'Dashboard data temporarily unavailable' });
|
|
909
|
+
}
|
|
910
|
+
});
|
|
911
|
+
this.app.get('/dashboard/task', async (req, res) => {
|
|
912
|
+
if (!this.requireDashOrApiKey(req, res))
|
|
913
|
+
return;
|
|
914
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
915
|
+
const { agentId, sessionId, taskId } = req.query;
|
|
916
|
+
const offset = Number(req.query.offset ?? 0);
|
|
917
|
+
if ([agentId, sessionId, taskId].some(v => typeof v !== 'string' || !v || v.length > 256) || !Number.isSafeInteger(offset) || offset < 0 || offset > 1000000) {
|
|
918
|
+
res.status(400).json({ error: 'Valid agentId, sessionId, taskId and offset are required' });
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
const source = this.agents.get(String(agentId))?.getDashboardSource?.();
|
|
922
|
+
if (!source) {
|
|
923
|
+
res.status(404).json({ error: 'Unknown agent' });
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
try {
|
|
927
|
+
const task = await this.dashboardReader.read('task', source.filename, { sessionId, taskId, offset, since: (0, dashboard_range_1.dashboardSince)(req.query.scope, Date.now(), this.gatewayConfig?.gateway?.timezone) });
|
|
928
|
+
if (!task) {
|
|
929
|
+
res.status(404).json({ error: 'Task not found in this session' });
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
res.json(task);
|
|
933
|
+
}
|
|
934
|
+
catch {
|
|
935
|
+
res.status(503).json({ error: 'Dashboard data temporarily unavailable' });
|
|
804
936
|
}
|
|
805
937
|
});
|
|
806
938
|
// Dashboard login — exchange a configured API key for an HttpOnly session
|
|
@@ -1030,6 +1162,66 @@ class GatewayRouter {
|
|
|
1030
1162
|
res.status(500).json({ error: 'Failed to list knowledge sources' });
|
|
1031
1163
|
}
|
|
1032
1164
|
});
|
|
1165
|
+
let memorySnapshot;
|
|
1166
|
+
let memorySnapshotUntil = 0;
|
|
1167
|
+
this.app.get('/dashboard/memory-activity', async (req, res) => {
|
|
1168
|
+
if (!this.requireDashOrApiKey(req, res))
|
|
1169
|
+
return;
|
|
1170
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
1171
|
+
const { agentId, kind, status, id, scope, completedOnly } = req.query, page = Number(req.query.page ?? 0);
|
|
1172
|
+
if (!Number.isSafeInteger(page) || page < 0 || page > 1000000 ||
|
|
1173
|
+
[agentId, kind, status, id, scope].some(v => v !== undefined && (typeof v !== 'string' || v.length > 256)) ||
|
|
1174
|
+
(completedOnly !== undefined && completedOnly !== 'true' && completedOnly !== 'false') ||
|
|
1175
|
+
(kind !== undefined && !['all', 'memory_dream', 'session_compaction'].includes(String(kind))) ||
|
|
1176
|
+
(status !== undefined && !String(status).split(',').every(s => ['all', 'none', 'pending', 'running', 'completed', 'failed', 'partial_failure', 'interrupted', 'skipped'].includes(s))) ||
|
|
1177
|
+
(scope !== undefined && !['24h', '7d', '30d', '90d', 'all'].includes(String(scope)))) {
|
|
1178
|
+
res.status(400).json({ error: 'Invalid activity filters' });
|
|
1179
|
+
return;
|
|
1180
|
+
}
|
|
1181
|
+
if (agentId && !this.agents.has(String(agentId))) {
|
|
1182
|
+
res.status(404).json({ error: 'Unknown agent' });
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
try {
|
|
1186
|
+
if (!memorySnapshot || Date.now() >= memorySnapshotUntil) {
|
|
1187
|
+
memorySnapshotUntil = Infinity;
|
|
1188
|
+
const snapshot = (0, memory_activity_1.readMemoryActivity)(this.agents, this.agentsRoot(), (filename, agentId) => this.dashboardReader.read('compaction', filename, { agentId })).then(data => { if (memorySnapshot === snapshot)
|
|
1189
|
+
memorySnapshotUntil = Date.now() + 5000; return data; }, error => { if (memorySnapshot === snapshot)
|
|
1190
|
+
memorySnapshot = undefined; throw error; });
|
|
1191
|
+
memorySnapshot = snapshot;
|
|
1192
|
+
}
|
|
1193
|
+
const data = await memorySnapshot;
|
|
1194
|
+
if (id) {
|
|
1195
|
+
let run = data.runs.find(r => r.id === id && r.agent === agentId);
|
|
1196
|
+
if (!run) {
|
|
1197
|
+
res.status(404).json({ error: 'Activity not found' });
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
const source = this.agents.get(String(agentId))?.getDashboardSource?.();
|
|
1201
|
+
if (run.kind === 'session_compaction' && source)
|
|
1202
|
+
run = await this.dashboardReader.read('compaction', source.filename, { agentId, runId: id });
|
|
1203
|
+
if (!run) {
|
|
1204
|
+
res.status(404).json({ error: 'Activity not found' });
|
|
1205
|
+
return;
|
|
1206
|
+
}
|
|
1207
|
+
if (completedOnly === 'true' && run.kind === 'session_compaction') {
|
|
1208
|
+
run = { ...run, items: (run.items ?? []).filter((item) => item.status === 'completed'), itemCount: run.completedSessions };
|
|
1209
|
+
}
|
|
1210
|
+
res.json({ run });
|
|
1211
|
+
return;
|
|
1212
|
+
}
|
|
1213
|
+
const timezone = this.gatewayConfig?.gateway?.timezone || 'UTC';
|
|
1214
|
+
const since = (0, dashboard_range_1.dashboardSince)(scope, Date.now(), timezone);
|
|
1215
|
+
const runs = data.runs.filter(r => (completedOnly !== 'true' || r.kind !== 'session_compaction' || r.completedSessions > 0 || (r.items ?? []).some((i) => i.status === 'completed')) && (!agentId || r.agent === agentId) && (!kind || kind === 'all' || r.kind === kind) && (!status || status === 'all' || String(status).split(',').includes(r.status)) && r.startedAt >= since);
|
|
1216
|
+
res.json({ timezone, since, runs: runs.slice(page * 25, page * 25 + 25).map(memory_activity_1.activitySummary), total: runs.length, page, pageSize: 25,
|
|
1217
|
+
agents: data.agents, schedules: data.schedules.filter(s => !agentId || s.agent === agentId), unavailable: data.unavailable,
|
|
1218
|
+
counts: { compactedSessions: runs.reduce((n, r) => n + (r.completedSessions ?? r.items?.filter((i) => i.status === 'completed').length ?? 0), 0), measuredReduction: runs.reduce((n, r) => n + ((0, memory_activity_1.activitySummary)(r).measuredReduction ?? 0), 0), measuredSessions: runs.reduce((n, r) => n + ((0, memory_activity_1.activitySummary)(r).measuredSessions ?? 0), 0), runs: runs.length, pendingProposals: runs.reduce((n, r) => n + (r.pendingProposals ?? 0), 0), failed: runs.filter(r => ['failed', 'partial_failure', 'interrupted'].includes(r.status)).length },
|
|
1219
|
+
historyLimit: 100 });
|
|
1220
|
+
}
|
|
1221
|
+
catch {
|
|
1222
|
+
res.status(503).json({ error: 'Memory activity temporarily unavailable' });
|
|
1223
|
+
}
|
|
1224
|
+
});
|
|
1033
1225
|
// Nightly dreaming report — parses every agent's `.dreaming/` audit trail
|
|
1034
1226
|
// (DREAMS.md + promotions.jsonl) into newest-first runs for the dashboard's
|
|
1035
1227
|
// "Nightly dreaming" tab. Auth: dashboard session cookie OR API key. Payload is
|
|
@@ -1044,7 +1236,7 @@ class GatewayRouter {
|
|
|
1044
1236
|
const runs = [];
|
|
1045
1237
|
const agents = [];
|
|
1046
1238
|
for (const id of this.agents.keys()) {
|
|
1047
|
-
const dir = path.join(root, id, 'workspace', audit_1.DREAMING_DIR);
|
|
1239
|
+
const dir = path.join(this.agents.get(id)?.workspacePath ?? path.join(root, id, 'workspace'), audit_1.DREAMING_DIR);
|
|
1048
1240
|
let dreams;
|
|
1049
1241
|
try {
|
|
1050
1242
|
dreams = fs.readFileSync(path.join(dir, 'DREAMS.md'), 'utf8');
|
|
@@ -1100,6 +1292,7 @@ class GatewayRouter {
|
|
|
1100
1292
|
this.app.post('/knowledge/dreams/apply', (req, res) => {
|
|
1101
1293
|
if (!this.requireDashOrApiKey(req, res))
|
|
1102
1294
|
return;
|
|
1295
|
+
memorySnapshot = undefined;
|
|
1103
1296
|
try {
|
|
1104
1297
|
const body = (req.body ?? {});
|
|
1105
1298
|
const agentId = typeof body.agentId === 'string' ? body.agentId : '';
|
|
@@ -1123,7 +1316,7 @@ class GatewayRouter {
|
|
|
1123
1316
|
}
|
|
1124
1317
|
indexes = body.indexes;
|
|
1125
1318
|
}
|
|
1126
|
-
const workspaceDir = path.join(this.agentsRoot(), agentId, 'workspace');
|
|
1319
|
+
const workspaceDir = this.agents.get(agentId)?.workspacePath ?? path.join(this.agentsRoot(), agentId, 'workspace');
|
|
1127
1320
|
const budget = (0, workspace_loader_1.resolveMemoryBudget)({
|
|
1128
1321
|
...this.gatewayConfig?.gateway?.memory,
|
|
1129
1322
|
...this.configs.get(agentId)?.memory,
|
|
@@ -1177,12 +1370,9 @@ class GatewayRouter {
|
|
|
1177
1370
|
res.status(500).json({ error: 'Failed to read screen state' });
|
|
1178
1371
|
});
|
|
1179
1372
|
});
|
|
1180
|
-
|
|
1181
|
-
this.app.get('/status', (req, res) => {
|
|
1182
|
-
if (!this.requireDashOrApiKey(req, res))
|
|
1183
|
-
return;
|
|
1373
|
+
const dashboardSnapshot = async (offset, scope = 'all') => {
|
|
1184
1374
|
const uptimeMs = Date.now() - this.startedAt.getTime();
|
|
1185
|
-
const agentsStatus = [...this.agents.entries()].map(([id, runner]) => {
|
|
1375
|
+
const agentsStatus = await Promise.all([...this.agents.entries()].map(async ([id, runner]) => {
|
|
1186
1376
|
const scheduler = this.schedulers.get(id);
|
|
1187
1377
|
const history = scheduler?.getHistory();
|
|
1188
1378
|
const agentConfig = this.configs.get(id);
|
|
@@ -1206,10 +1396,20 @@ class GatewayRouter {
|
|
|
1206
1396
|
}).filter(Boolean);
|
|
1207
1397
|
const lastActivity = this.lastActivityAt.get(id);
|
|
1208
1398
|
// PTY streams are keyed per session, so liveness is per session too.
|
|
1209
|
-
const
|
|
1210
|
-
const
|
|
1211
|
-
const
|
|
1212
|
-
const
|
|
1399
|
+
const source = runner.getDashboardSource?.();
|
|
1400
|
+
const legacySessions = runner.getSessionsSummary();
|
|
1401
|
+
const liveSessionIds = new Set(runner.liveSessionIds?.() ?? []);
|
|
1402
|
+
const orchestration = source ? (source.enabled ? await this.dashboardReader.read('summary', source.filename, { ...source, timezone: this.gatewayConfig?.gateway?.timezone, offset, since: (0, dashboard_range_1.dashboardSince)(scope, Date.now(), this.gatewayConfig?.gateway?.timezone), legacyIds: legacySessions.map(s => s.sessionId) }) : undefined) : runner.getOrchestrationSummary?.();
|
|
1403
|
+
const managedSessions = (orchestration?.sessions ?? []).map((s) => ({
|
|
1404
|
+
...s,
|
|
1405
|
+
// Live liveness from the runner's in-memory map: the DB-derived status
|
|
1406
|
+
// reports 'idle' for both a kept-alive session and one already killed by
|
|
1407
|
+
// the idle cleaner; sessionAlive disambiguates them for the dashboard.
|
|
1408
|
+
sessionAlive: liveSessionIds.has(s.sessionId),
|
|
1409
|
+
...(runner.isSessionCompacting?.(s.sessionId) ? { status: 'compacting', isRunning: true } : {}),
|
|
1410
|
+
}));
|
|
1411
|
+
const managedIds = new Set([...managedSessions.map((s) => s.sessionId), ...(orchestration?.managedLegacyIds ?? [])]);
|
|
1412
|
+
const sessions = [...legacySessions.filter(s => !managedIds.has(s.sessionId) && (!(0, dashboard_range_1.dashboardSince)(scope, Date.now(), this.gatewayConfig?.gateway?.timezone) || Number(s.updatedAt || s.spawnedAt || 0) >= (0, dashboard_range_1.dashboardSince)(scope, Date.now(), this.gatewayConfig?.gateway?.timezone))), ...managedSessions].map((s) => ({
|
|
1213
1413
|
...s,
|
|
1214
1414
|
hasPtyStream: pty_stream_registry_1.ptyStreamRegistry.hasSockets(s.sessionId),
|
|
1215
1415
|
}));
|
|
@@ -1235,15 +1435,118 @@ class GatewayRouter {
|
|
|
1235
1435
|
},
|
|
1236
1436
|
sessions,
|
|
1237
1437
|
};
|
|
1238
|
-
});
|
|
1239
|
-
|
|
1438
|
+
}));
|
|
1439
|
+
return {
|
|
1240
1440
|
agents: agentsStatus,
|
|
1241
1441
|
uptime: Math.floor(uptimeMs / 1000),
|
|
1242
1442
|
startedAt: this.startedAt.toISOString(),
|
|
1243
1443
|
version: GATEWAY_VERSION,
|
|
1244
1444
|
// Degraded file watchers (e.g. inotify ENOSPC). Empty array when healthy.
|
|
1245
1445
|
watchers: (0, factory_1.getWatcherHealth)(),
|
|
1246
|
-
}
|
|
1446
|
+
};
|
|
1447
|
+
};
|
|
1448
|
+
this.app.get('/dashboard/charts', async (req, res) => {
|
|
1449
|
+
if (!this.requireDashOrApiKey(req, res))
|
|
1450
|
+
return;
|
|
1451
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
1452
|
+
const scope = req.query.scope ?? '24h';
|
|
1453
|
+
if (typeof scope !== 'string' || !['24h', '7d', '30d', '90d'].includes(scope)) {
|
|
1454
|
+
res.status(400).json({ error: 'Invalid chart range' });
|
|
1455
|
+
return;
|
|
1456
|
+
}
|
|
1457
|
+
const timezone = this.gatewayConfig?.gateway?.timezone || 'UTC';
|
|
1458
|
+
// Stable snapshot timestamp coalesces all four cards and concurrent browsers.
|
|
1459
|
+
const now = Math.floor(Date.now() / 10000) * 10000;
|
|
1460
|
+
try {
|
|
1461
|
+
const agents = [];
|
|
1462
|
+
for (const [id, runner] of this.agents) {
|
|
1463
|
+
const source = runner.getDashboardSource?.();
|
|
1464
|
+
if (!source?.enabled)
|
|
1465
|
+
continue;
|
|
1466
|
+
const usage = await this.dashboardReader.read('charts', source.filename, { scope, timezone, now });
|
|
1467
|
+
if (usage)
|
|
1468
|
+
agents.push({ id, ...usage });
|
|
1469
|
+
}
|
|
1470
|
+
res.json({ scope, timezone, asOf: now, since: (0, dashboard_range_1.dashboardSince)(scope, now, timezone), agents });
|
|
1471
|
+
}
|
|
1472
|
+
catch {
|
|
1473
|
+
res.status(503).json({ error: 'Chart data temporarily unavailable' });
|
|
1474
|
+
}
|
|
1475
|
+
});
|
|
1476
|
+
this.app.get('/status', async (req, res) => {
|
|
1477
|
+
if (!this.requireDashOrApiKey(req, res))
|
|
1478
|
+
return;
|
|
1479
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
1480
|
+
const offset = Number(req.query.offset ?? 0);
|
|
1481
|
+
if (!Number.isSafeInteger(offset) || offset < 0 || offset > 1000000) {
|
|
1482
|
+
res.status(400).json({ error: 'Invalid offset' });
|
|
1483
|
+
return;
|
|
1484
|
+
}
|
|
1485
|
+
try {
|
|
1486
|
+
res.json(await dashboardSnapshot(offset, req.query.scope ?? 'all'));
|
|
1487
|
+
}
|
|
1488
|
+
catch {
|
|
1489
|
+
res.status(503).json({ error: 'Dashboard data temporarily unavailable' });
|
|
1490
|
+
}
|
|
1491
|
+
});
|
|
1492
|
+
this.app.get('/dashboard/events', async (req, res) => {
|
|
1493
|
+
if (!this.requireDashOrApiKey(req, res))
|
|
1494
|
+
return;
|
|
1495
|
+
const offset = Number(req.query.offset ?? 0);
|
|
1496
|
+
if (!Number.isSafeInteger(offset) || offset < 0 || offset > 1000000) {
|
|
1497
|
+
res.status(400).json({ error: 'Invalid offset' });
|
|
1498
|
+
return;
|
|
1499
|
+
}
|
|
1500
|
+
if (this.dashboardStreams.size >= 32) {
|
|
1501
|
+
res.status(503).json({ error: 'Too many dashboard streams' });
|
|
1502
|
+
return;
|
|
1503
|
+
}
|
|
1504
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
1505
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
1506
|
+
res.setHeader('X-Accel-Buffering', 'no');
|
|
1507
|
+
res.flushHeaders();
|
|
1508
|
+
this.dashboardStreams.add(res);
|
|
1509
|
+
let closed = false, busy = false, last = String(req.headers['last-event-id'] ?? '');
|
|
1510
|
+
const stop = () => { closed = true; clearInterval(timer); this.dashboardStreams.delete(res); res.end(); };
|
|
1511
|
+
const tick = async () => {
|
|
1512
|
+
if (closed || busy)
|
|
1513
|
+
return;
|
|
1514
|
+
// Recheck cookie expiration/revocation without writing HTTP errors into SSE.
|
|
1515
|
+
if (this.apiKeys.length && !this.hasValidDashSession(req) && !timingSafeAdminKeyMatch(this.apiKeys, this.extractApiToken(req))) {
|
|
1516
|
+
res.write('event: unauthorized\ndata: {}\n\n');
|
|
1517
|
+
stop();
|
|
1518
|
+
return;
|
|
1519
|
+
}
|
|
1520
|
+
// Never queue snapshots behind a slow browser.
|
|
1521
|
+
if (res.writableLength > 256 * 1024) {
|
|
1522
|
+
stop();
|
|
1523
|
+
return;
|
|
1524
|
+
}
|
|
1525
|
+
busy = true;
|
|
1526
|
+
try {
|
|
1527
|
+
const data = await dashboardSnapshot(offset, req.query.scope ?? 'all');
|
|
1528
|
+
if (closed)
|
|
1529
|
+
return;
|
|
1530
|
+
const payload = JSON.stringify(data), id = crypto.createHash('sha256').update(JSON.stringify({ agents: data.agents, watchers: data.watchers, version: data.version })).digest('hex').slice(0, 24);
|
|
1531
|
+
if (id !== last) {
|
|
1532
|
+
res.write('id: ' + id + '\nevent: snapshot\ndata: ' + payload + '\n\n');
|
|
1533
|
+
last = id;
|
|
1534
|
+
}
|
|
1535
|
+
else
|
|
1536
|
+
res.write(': heartbeat\n\n');
|
|
1537
|
+
}
|
|
1538
|
+
catch {
|
|
1539
|
+
if (!closed)
|
|
1540
|
+
res.write('event: unavailable\ndata: {}\n\n');
|
|
1541
|
+
}
|
|
1542
|
+
finally {
|
|
1543
|
+
busy = false;
|
|
1544
|
+
}
|
|
1545
|
+
};
|
|
1546
|
+
const timer = setInterval(() => void tick(), 5000);
|
|
1547
|
+
timer.unref();
|
|
1548
|
+
req.on('close', stop);
|
|
1549
|
+
void tick();
|
|
1247
1550
|
});
|
|
1248
1551
|
}
|
|
1249
1552
|
/** The port the HTTP server is actually listening on, or null when it is not
|
|
@@ -1435,6 +1738,10 @@ class GatewayRouter {
|
|
|
1435
1738
|
}
|
|
1436
1739
|
}
|
|
1437
1740
|
async stop() {
|
|
1741
|
+
for (const response of this.dashboardStreams)
|
|
1742
|
+
response.end();
|
|
1743
|
+
this.dashboardStreams.clear();
|
|
1744
|
+
await this.dashboardReader.close();
|
|
1438
1745
|
await this.voiceApi?.close();
|
|
1439
1746
|
if (this.ticketPruner)
|
|
1440
1747
|
clearInterval(this.ticketPruner);
|