@poolzin/pool-bot 2026.2.0 → 2026.2.1
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/dist/agents/bash-tools.exec.js +76 -25
- package/dist/agents/cli-runner/helpers.js +9 -11
- package/dist/agents/identity.js +47 -7
- package/dist/agents/memory-search.js +25 -8
- package/dist/agents/model-selection.js +21 -0
- package/dist/agents/pi-embedded-block-chunker.js +117 -42
- package/dist/agents/pi-embedded-helpers/errors.js +183 -78
- package/dist/agents/pi-embedded-helpers.js +1 -1
- package/dist/agents/pi-embedded-runner/compact.js +1 -0
- package/dist/agents/pi-embedded-runner/model.js +61 -2
- package/dist/agents/pi-embedded-runner/run/attempt.js +21 -11
- package/dist/agents/pi-embedded-runner/run.js +199 -46
- package/dist/agents/pi-embedded-runner/system-prompt.js +10 -2
- package/dist/agents/pi-embedded-subscribe.js +118 -29
- package/dist/agents/pi-tools.js +10 -5
- package/dist/agents/poolbot-tools.js +15 -10
- package/dist/agents/sandbox-paths.js +31 -0
- package/dist/agents/session-tool-result-guard.js +94 -15
- package/dist/agents/shell-utils.js +51 -0
- package/dist/agents/skills/bundled-context.js +23 -0
- package/dist/agents/skills/bundled-dir.js +41 -7
- package/dist/agents/skills-install.js +60 -23
- package/dist/agents/subagent-announce.js +79 -34
- package/dist/agents/tool-policy.conformance.js +14 -0
- package/dist/agents/tool-policy.js +24 -0
- package/dist/agents/tools/cron-tool.js +166 -19
- package/dist/agents/tools/discord-actions-presence.js +78 -0
- package/dist/agents/tools/message-tool.js +56 -2
- package/dist/agents/tools/sessions-history-tool.js +69 -1
- package/dist/agents/tools/web-search.js +211 -42
- package/dist/agents/usage.js +23 -1
- package/dist/agents/workspace-run.js +67 -0
- package/dist/agents/workspace-templates.js +44 -0
- package/dist/auto-reply/command-auth.js +121 -6
- package/dist/auto-reply/envelope.js +50 -72
- package/dist/auto-reply/reply/commands-compact.js +1 -0
- package/dist/auto-reply/reply/commands-context-report.js +1 -0
- package/dist/auto-reply/reply/commands-context.js +1 -0
- package/dist/auto-reply/reply/commands-models.js +107 -60
- package/dist/auto-reply/reply/commands-ptt.js +171 -0
- package/dist/auto-reply/reply/get-reply-run.js +2 -1
- package/dist/auto-reply/reply/inbound-context.js +5 -1
- package/dist/auto-reply/reply/model-selection.js +3 -3
- package/dist/auto-reply/thinking.js +88 -43
- package/dist/browser/bridge-server.js +13 -0
- package/dist/browser/cdp.helpers.js +38 -24
- package/dist/browser/client-fetch.js +50 -7
- package/dist/browser/config.js +1 -10
- package/dist/browser/extension-relay.js +101 -40
- package/dist/browser/pw-ai.js +1 -1
- package/dist/browser/pw-session.js +143 -8
- package/dist/browser/pw-tools-core.interactions.js +125 -27
- package/dist/browser/pw-tools-core.responses.js +1 -1
- package/dist/browser/pw-tools-core.state.js +1 -1
- package/dist/browser/routes/agent.act.js +86 -41
- package/dist/browser/routes/dispatcher.js +4 -4
- package/dist/browser/screenshot.js +1 -1
- package/dist/browser/server.js +13 -0
- package/dist/build-info.json +3 -3
- package/dist/channels/reply-prefix.js +8 -1
- package/dist/cli/cron-cli/register.cron-add.js +61 -40
- package/dist/cli/cron-cli/register.cron-edit.js +60 -34
- package/dist/cli/cron-cli/shared.js +56 -41
- package/dist/cli/dns-cli.js +26 -14
- package/dist/cli/gateway-cli/register.js +37 -19
- package/dist/cli/memory-cli.js +5 -5
- package/dist/cli/parse-bytes.js +37 -0
- package/dist/cli/update-cli.js +173 -52
- package/dist/commands/agent.js +1 -0
- package/dist/commands/doctor-config-flow.js +61 -5
- package/dist/commands/doctor-state-migrations.js +1 -1
- package/dist/commands/health.js +1 -1
- package/dist/commands/model-allowlist.js +29 -0
- package/dist/commands/model-picker.js +2 -1
- package/dist/commands/models/list.status-command.js +43 -23
- package/dist/commands/models/shared.js +15 -0
- package/dist/commands/onboard-custom.js +384 -0
- package/dist/commands/onboard-non-interactive/local/auth-choice-inference.js +35 -0
- package/dist/commands/onboard-non-interactive/local/auth-choice.js +6 -3
- package/dist/commands/onboard-skills.js +63 -38
- package/dist/commands/openai-model-default.js +41 -0
- package/dist/config/defaults.js +3 -2
- package/dist/config/paths.js +136 -35
- package/dist/config/plugin-auto-enable.js +21 -5
- package/dist/config/redact-snapshot.js +153 -0
- package/dist/config/schema.field-metadata.js +590 -0
- package/dist/config/schema.js +2 -2
- package/dist/config/sessions/store.js +291 -23
- package/dist/config/zod-schema.agent-defaults.js +3 -0
- package/dist/config/zod-schema.agent-runtime.js +13 -2
- package/dist/config/zod-schema.providers-core.js +142 -0
- package/dist/config/zod-schema.session.js +3 -0
- package/dist/cron/delivery.js +57 -0
- package/dist/cron/isolated-agent/delivery-target.js +18 -3
- package/dist/cron/isolated-agent/helpers.js +22 -5
- package/dist/cron/isolated-agent/run.js +171 -63
- package/dist/cron/isolated-agent/session.js +2 -0
- package/dist/cron/normalize.js +356 -28
- package/dist/cron/parse.js +10 -5
- package/dist/cron/run-log.js +35 -10
- package/dist/cron/schedule.js +41 -6
- package/dist/cron/service/jobs.js +208 -35
- package/dist/cron/service/ops.js +72 -16
- package/dist/cron/service/state.js +2 -0
- package/dist/cron/service/store.js +386 -14
- package/dist/cron/service/timer.js +390 -147
- package/dist/cron/session-reaper.js +86 -0
- package/dist/cron/store.js +23 -8
- package/dist/cron/validate-timestamp.js +43 -0
- package/dist/discord/monitor/agent-components.js +438 -0
- package/dist/discord/monitor/allow-list.js +28 -5
- package/dist/discord/monitor/gateway-registry.js +29 -0
- package/dist/discord/monitor/native-command.js +44 -23
- package/dist/discord/monitor/sender-identity.js +45 -0
- package/dist/discord/pluralkit.js +27 -0
- package/dist/discord/send.outbound.js +92 -5
- package/dist/discord/send.shared.js +60 -23
- package/dist/discord/targets.js +84 -1
- package/dist/entry.js +15 -9
- package/dist/extensionAPI.js +8 -0
- package/dist/gateway/control-ui.js +8 -1
- package/dist/gateway/hooks-mapping.js +3 -0
- package/dist/gateway/hooks.js +65 -0
- package/dist/gateway/net.js +96 -31
- package/dist/gateway/node-command-policy.js +50 -15
- package/dist/gateway/origin-check.js +56 -0
- package/dist/gateway/protocol/client-info.js +9 -0
- package/dist/gateway/protocol/index.js +9 -2
- package/dist/gateway/protocol/schema/agents-models-skills.js +71 -1
- package/dist/gateway/protocol/schema/cron.js +22 -10
- package/dist/gateway/protocol/schema/protocol-schemas.js +16 -2
- package/dist/gateway/protocol/schema/sessions.js +12 -0
- package/dist/gateway/server/hooks.js +1 -1
- package/dist/gateway/server-broadcast.js +26 -9
- package/dist/gateway/server-chat.js +112 -23
- package/dist/gateway/server-discovery-runtime.js +10 -2
- package/dist/gateway/server-http.js +109 -11
- package/dist/gateway/server-methods/agent-timestamp.js +60 -0
- package/dist/gateway/server-methods/agents.js +321 -2
- package/dist/gateway/server-methods/usage.js +559 -16
- package/dist/gateway/server-runtime-state.js +22 -8
- package/dist/gateway/server-startup-memory.js +16 -0
- package/dist/gateway/server.impl.js +5 -1
- package/dist/gateway/session-utils.fs.js +23 -25
- package/dist/gateway/session-utils.js +20 -10
- package/dist/gateway/sessions-patch.js +7 -22
- package/dist/gateway/test-helpers.server.js +35 -2
- package/dist/imessage/constants.js +2 -0
- package/dist/imessage/monitor/deliver.js +4 -1
- package/dist/imessage/monitor/monitor-provider.js +51 -1
- package/dist/infra/bonjour-discovery.js +131 -70
- package/dist/infra/control-ui-assets.js +134 -12
- package/dist/infra/errors.js +12 -0
- package/dist/infra/exec-approvals.js +266 -57
- package/dist/infra/format-time/format-datetime.js +79 -0
- package/dist/infra/format-time/format-duration.js +81 -0
- package/dist/infra/format-time/format-relative.js +80 -0
- package/dist/infra/heartbeat-runner.js +140 -49
- package/dist/infra/home-dir.js +54 -0
- package/dist/infra/net/fetch-guard.js +122 -0
- package/dist/infra/net/ssrf.js +65 -29
- package/dist/infra/outbound/abort.js +14 -0
- package/dist/infra/outbound/message-action-runner.js +77 -13
- package/dist/infra/outbound/outbound-session.js +143 -37
- package/dist/infra/poolbot-root.js +43 -1
- package/dist/infra/session-cost-usage.js +631 -41
- package/dist/infra/state-migrations.js +317 -47
- package/dist/infra/update-global.js +35 -0
- package/dist/infra/update-runner.js +149 -43
- package/dist/infra/warning-filter.js +65 -0
- package/dist/infra/widearea-dns.js +30 -9
- package/dist/logging/redact-identifier.js +12 -0
- package/dist/media/fetch.js +81 -58
- package/dist/media-understanding/apply.js +403 -3
- package/dist/media-understanding/attachments.js +38 -27
- package/dist/media-understanding/defaults.js +16 -0
- package/dist/media-understanding/providers/deepgram/audio.js +22 -14
- package/dist/media-understanding/providers/google/audio.js +24 -17
- package/dist/media-understanding/providers/google/video.js +24 -17
- package/dist/media-understanding/providers/image.js +2 -2
- package/dist/media-understanding/providers/index.js +4 -1
- package/dist/media-understanding/providers/openai/audio.js +22 -14
- package/dist/media-understanding/providers/shared.js +16 -11
- package/dist/media-understanding/providers/zai/index.js +6 -0
- package/dist/media-understanding/runner.js +158 -90
- package/dist/memory/batch-voyage.js +277 -0
- package/dist/memory/embeddings-voyage.js +75 -0
- package/dist/memory/embeddings.js +28 -16
- package/dist/memory/internal.js +101 -18
- package/dist/memory/manager.js +154 -48
- package/dist/memory/search-manager.js +173 -0
- package/dist/memory/session-files.js +9 -3
- package/dist/node-host/runner.js +34 -24
- package/dist/node-host/with-timeout.js +27 -0
- package/dist/plugins/commands.js +5 -1
- package/dist/plugins/config-state.js +86 -7
- package/dist/plugins/source-display.js +51 -0
- package/dist/process/exec.js +20 -2
- package/dist/routing/resolve-route.js +12 -0
- package/dist/routing/session-key.js +15 -0
- package/dist/runtime.js +2 -0
- package/dist/security/audit-extra.async.js +601 -0
- package/dist/security/audit-extra.js +2 -830
- package/dist/security/audit-extra.sync.js +505 -0
- package/dist/security/channel-metadata.js +34 -0
- package/dist/security/external-content.js +88 -6
- package/dist/security/skill-scanner.js +330 -0
- package/dist/sessions/session-key-utils.js +7 -0
- package/dist/signal/monitor/event-handler.js +80 -1
- package/dist/slack/monitor/media.js +85 -15
- package/dist/tailscale/detect.js +1 -2
- package/dist/telegram/bot/helpers.js +109 -28
- package/dist/telegram/bot-handlers.js +144 -3
- package/dist/telegram/bot-message-context.js +37 -10
- package/dist/telegram/bot-message-dispatch.js +48 -15
- package/dist/telegram/bot-native-commands.js +86 -29
- package/dist/telegram/bot.js +30 -29
- package/dist/telegram/model-buttons.js +163 -0
- package/dist/telegram/monitor.js +110 -85
- package/dist/telegram/send.js +129 -47
- package/dist/terminal/restore.js +45 -0
- package/dist/test-helpers/state-dir-env.js +16 -0
- package/dist/tts/tts.js +12 -6
- package/dist/tui/tui-session-actions.js +166 -54
- package/dist/utils/fetch-timeout.js +20 -0
- package/dist/utils/normalize-secret-input.js +19 -0
- package/dist/utils/transcript-tools.js +58 -0
- package/dist/utils.js +45 -14
- package/dist/version.js +42 -5
- package/package.json +1 -1
|
@@ -1,54 +1,134 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
1
2
|
import { loadConfig } from "../../config/config.js";
|
|
2
|
-
import {
|
|
3
|
+
import { resolveSessionFilePath } from "../../config/sessions/paths.js";
|
|
3
4
|
import { loadProviderUsageSummary } from "../../infra/provider-usage.js";
|
|
5
|
+
import { loadCostUsageSummary, loadSessionCostSummary, loadSessionUsageTimeSeries, discoverAllSessions, } from "../../infra/session-cost-usage.js";
|
|
6
|
+
import { parseAgentSessionKey } from "../../routing/session-key.js";
|
|
7
|
+
import { ErrorCodes, errorShape, formatValidationErrors, validateSessionsUsageParams, } from "../protocol/index.js";
|
|
8
|
+
import { listAgentsForGateway, loadCombinedSessionStoreForGateway, loadSessionEntry, } from "../session-utils.js";
|
|
4
9
|
const COST_USAGE_CACHE_TTL_MS = 30_000;
|
|
5
10
|
const costUsageCache = new Map();
|
|
11
|
+
/**
|
|
12
|
+
* Parse a date string (YYYY-MM-DD) to start of day timestamp in UTC.
|
|
13
|
+
* Returns undefined if invalid.
|
|
14
|
+
*/
|
|
15
|
+
const parseDateToMs = (raw) => {
|
|
16
|
+
if (typeof raw !== "string" || !raw.trim()) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(raw.trim());
|
|
20
|
+
if (!match) {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
const [, year, month, day] = match;
|
|
24
|
+
// Use UTC to ensure consistent behavior across timezones
|
|
25
|
+
const ms = Date.UTC(parseInt(year), parseInt(month) - 1, parseInt(day));
|
|
26
|
+
if (Number.isNaN(ms)) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
return ms;
|
|
30
|
+
};
|
|
6
31
|
const parseDays = (raw) => {
|
|
7
|
-
if (typeof raw === "number" && Number.isFinite(raw))
|
|
32
|
+
if (typeof raw === "number" && Number.isFinite(raw)) {
|
|
8
33
|
return Math.floor(raw);
|
|
34
|
+
}
|
|
9
35
|
if (typeof raw === "string" && raw.trim() !== "") {
|
|
10
36
|
const parsed = Number(raw);
|
|
11
|
-
if (Number.isFinite(parsed))
|
|
37
|
+
if (Number.isFinite(parsed)) {
|
|
12
38
|
return Math.floor(parsed);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Get date range from params (startDate/endDate or days).
|
|
45
|
+
* Falls back to last 30 days if not provided.
|
|
46
|
+
*/
|
|
47
|
+
const parseDateRange = (params) => {
|
|
48
|
+
const now = new Date();
|
|
49
|
+
// Use UTC for consistent date handling
|
|
50
|
+
const todayStartMs = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate());
|
|
51
|
+
const todayEndMs = todayStartMs + 24 * 60 * 60 * 1000 - 1;
|
|
52
|
+
const startMs = parseDateToMs(params.startDate);
|
|
53
|
+
const endMs = parseDateToMs(params.endDate);
|
|
54
|
+
if (startMs !== undefined && endMs !== undefined) {
|
|
55
|
+
// endMs should be end of day
|
|
56
|
+
return { startMs, endMs: endMs + 24 * 60 * 60 * 1000 - 1 };
|
|
57
|
+
}
|
|
58
|
+
const days = parseDays(params.days);
|
|
59
|
+
if (days !== undefined) {
|
|
60
|
+
const clampedDays = Math.max(1, days);
|
|
61
|
+
const start = todayStartMs - (clampedDays - 1) * 24 * 60 * 60 * 1000;
|
|
62
|
+
return { startMs: start, endMs: todayEndMs };
|
|
13
63
|
}
|
|
14
|
-
|
|
64
|
+
// Default to last 30 days
|
|
65
|
+
const defaultStartMs = todayStartMs - 29 * 24 * 60 * 60 * 1000;
|
|
66
|
+
return { startMs: defaultStartMs, endMs: todayEndMs };
|
|
15
67
|
};
|
|
68
|
+
async function discoverAllSessionsForUsage(params) {
|
|
69
|
+
const agents = listAgentsForGateway(params.config).agents;
|
|
70
|
+
const results = await Promise.all(agents.map(async (agent) => {
|
|
71
|
+
const sessions = await discoverAllSessions({
|
|
72
|
+
agentId: agent.id,
|
|
73
|
+
startMs: params.startMs,
|
|
74
|
+
endMs: params.endMs,
|
|
75
|
+
});
|
|
76
|
+
return sessions.map((session) => ({ ...session, agentId: agent.id }));
|
|
77
|
+
}));
|
|
78
|
+
return results.flat().toSorted((a, b) => b.mtime - a.mtime);
|
|
79
|
+
}
|
|
16
80
|
async function loadCostUsageSummaryCached(params) {
|
|
17
|
-
const
|
|
81
|
+
const cacheKey = `${params.startMs}-${params.endMs}`;
|
|
18
82
|
const now = Date.now();
|
|
19
|
-
const cached = costUsageCache.get(
|
|
83
|
+
const cached = costUsageCache.get(cacheKey);
|
|
20
84
|
if (cached?.summary && cached.updatedAt && now - cached.updatedAt < COST_USAGE_CACHE_TTL_MS) {
|
|
21
85
|
return cached.summary;
|
|
22
86
|
}
|
|
23
87
|
if (cached?.inFlight) {
|
|
24
|
-
if (cached.summary)
|
|
88
|
+
if (cached.summary) {
|
|
25
89
|
return cached.summary;
|
|
90
|
+
}
|
|
26
91
|
return await cached.inFlight;
|
|
27
92
|
}
|
|
28
93
|
const entry = cached ?? {};
|
|
29
|
-
const inFlight = loadCostUsageSummary({
|
|
94
|
+
const inFlight = loadCostUsageSummary({
|
|
95
|
+
startMs: params.startMs,
|
|
96
|
+
endMs: params.endMs,
|
|
97
|
+
config: params.config,
|
|
98
|
+
})
|
|
30
99
|
.then((summary) => {
|
|
31
|
-
costUsageCache.set(
|
|
100
|
+
costUsageCache.set(cacheKey, { summary, updatedAt: Date.now() });
|
|
32
101
|
return summary;
|
|
33
102
|
})
|
|
34
103
|
.catch((err) => {
|
|
35
|
-
if (entry.summary)
|
|
104
|
+
if (entry.summary) {
|
|
36
105
|
return entry.summary;
|
|
106
|
+
}
|
|
37
107
|
throw err;
|
|
38
108
|
})
|
|
39
109
|
.finally(() => {
|
|
40
|
-
const current = costUsageCache.get(
|
|
110
|
+
const current = costUsageCache.get(cacheKey);
|
|
41
111
|
if (current?.inFlight === inFlight) {
|
|
42
112
|
current.inFlight = undefined;
|
|
43
|
-
costUsageCache.set(
|
|
113
|
+
costUsageCache.set(cacheKey, current);
|
|
44
114
|
}
|
|
45
115
|
});
|
|
46
116
|
entry.inFlight = inFlight;
|
|
47
|
-
costUsageCache.set(
|
|
48
|
-
if (entry.summary)
|
|
117
|
+
costUsageCache.set(cacheKey, entry);
|
|
118
|
+
if (entry.summary) {
|
|
49
119
|
return entry.summary;
|
|
120
|
+
}
|
|
50
121
|
return await inFlight;
|
|
51
122
|
}
|
|
123
|
+
// Exposed for unit tests (kept as a single export to avoid widening the public API surface).
|
|
124
|
+
export const __test = {
|
|
125
|
+
parseDateToMs,
|
|
126
|
+
parseDays,
|
|
127
|
+
parseDateRange,
|
|
128
|
+
discoverAllSessionsForUsage,
|
|
129
|
+
loadCostUsageSummaryCached,
|
|
130
|
+
costUsageCache,
|
|
131
|
+
};
|
|
52
132
|
export const usageHandlers = {
|
|
53
133
|
"usage.status": async ({ respond }) => {
|
|
54
134
|
const summary = await loadProviderUsageSummary();
|
|
@@ -56,8 +136,471 @@ export const usageHandlers = {
|
|
|
56
136
|
},
|
|
57
137
|
"usage.cost": async ({ respond, params }) => {
|
|
58
138
|
const config = loadConfig();
|
|
59
|
-
const
|
|
60
|
-
|
|
139
|
+
const { startMs, endMs } = parseDateRange({
|
|
140
|
+
startDate: params?.startDate,
|
|
141
|
+
endDate: params?.endDate,
|
|
142
|
+
days: params?.days,
|
|
143
|
+
});
|
|
144
|
+
const summary = await loadCostUsageSummaryCached({ startMs, endMs, config });
|
|
61
145
|
respond(true, summary, undefined);
|
|
62
146
|
},
|
|
147
|
+
"sessions.usage": async ({ respond, params }) => {
|
|
148
|
+
if (!validateSessionsUsageParams(params)) {
|
|
149
|
+
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, `invalid sessions.usage params: ${formatValidationErrors(validateSessionsUsageParams.errors)}`));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const p = params;
|
|
153
|
+
const config = loadConfig();
|
|
154
|
+
const { startMs, endMs } = parseDateRange({
|
|
155
|
+
startDate: p.startDate,
|
|
156
|
+
endDate: p.endDate,
|
|
157
|
+
});
|
|
158
|
+
const limit = typeof p.limit === "number" && Number.isFinite(p.limit) ? p.limit : 50;
|
|
159
|
+
const includeContextWeight = p.includeContextWeight ?? false;
|
|
160
|
+
const specificKey = typeof p.key === "string" ? p.key.trim() : null;
|
|
161
|
+
// Load session store for named sessions
|
|
162
|
+
const { store } = loadCombinedSessionStoreForGateway(config);
|
|
163
|
+
const now = Date.now();
|
|
164
|
+
const mergedEntries = [];
|
|
165
|
+
// Optimization: If a specific key is requested, skip full directory scan
|
|
166
|
+
if (specificKey) {
|
|
167
|
+
const parsed = parseAgentSessionKey(specificKey);
|
|
168
|
+
const agentIdFromKey = parsed?.agentId;
|
|
169
|
+
const keyRest = parsed?.rest ?? specificKey;
|
|
170
|
+
// Prefer the store entry when available, even if the caller provides a discovered key
|
|
171
|
+
// (`agent:<id>:<sessionId>`) for a session that now has a canonical store key.
|
|
172
|
+
const storeBySessionId = new Map();
|
|
173
|
+
for (const [key, entry] of Object.entries(store)) {
|
|
174
|
+
if (entry?.sessionId) {
|
|
175
|
+
storeBySessionId.set(entry.sessionId, { key, entry });
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const storeMatch = store[specificKey]
|
|
179
|
+
? { key: specificKey, entry: store[specificKey] }
|
|
180
|
+
: null;
|
|
181
|
+
const storeByIdMatch = storeBySessionId.get(keyRest) ?? null;
|
|
182
|
+
const resolvedStoreKey = storeMatch?.key ?? storeByIdMatch?.key ?? specificKey;
|
|
183
|
+
const storeEntry = storeMatch?.entry ?? storeByIdMatch?.entry;
|
|
184
|
+
const sessionId = storeEntry?.sessionId ?? keyRest;
|
|
185
|
+
// Resolve the session file path
|
|
186
|
+
const sessionFile = resolveSessionFilePath(sessionId, storeEntry, {
|
|
187
|
+
agentId: agentIdFromKey,
|
|
188
|
+
});
|
|
189
|
+
try {
|
|
190
|
+
const stats = fs.statSync(sessionFile);
|
|
191
|
+
if (stats.isFile()) {
|
|
192
|
+
mergedEntries.push({
|
|
193
|
+
key: resolvedStoreKey,
|
|
194
|
+
sessionId,
|
|
195
|
+
sessionFile,
|
|
196
|
+
label: storeEntry?.label,
|
|
197
|
+
updatedAt: storeEntry?.updatedAt ?? stats.mtimeMs,
|
|
198
|
+
storeEntry,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
// File doesn't exist - no results for this key
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
// Full discovery for list view
|
|
208
|
+
const discoveredSessions = await discoverAllSessionsForUsage({
|
|
209
|
+
config,
|
|
210
|
+
startMs,
|
|
211
|
+
endMs,
|
|
212
|
+
});
|
|
213
|
+
// Build a map of sessionId -> store entry for quick lookup
|
|
214
|
+
const storeBySessionId = new Map();
|
|
215
|
+
for (const [key, entry] of Object.entries(store)) {
|
|
216
|
+
if (entry?.sessionId) {
|
|
217
|
+
storeBySessionId.set(entry.sessionId, { key, entry });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
for (const discovered of discoveredSessions) {
|
|
221
|
+
const storeMatch = storeBySessionId.get(discovered.sessionId);
|
|
222
|
+
if (storeMatch) {
|
|
223
|
+
// Named session from store
|
|
224
|
+
mergedEntries.push({
|
|
225
|
+
key: storeMatch.key,
|
|
226
|
+
sessionId: discovered.sessionId,
|
|
227
|
+
sessionFile: discovered.sessionFile,
|
|
228
|
+
label: storeMatch.entry.label,
|
|
229
|
+
updatedAt: storeMatch.entry.updatedAt ?? discovered.mtime,
|
|
230
|
+
storeEntry: storeMatch.entry,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
// Unnamed session - use session ID as key, no label
|
|
235
|
+
mergedEntries.push({
|
|
236
|
+
// Keep agentId in the key so the dashboard can attribute sessions and later fetch logs.
|
|
237
|
+
key: `agent:${discovered.agentId}:${discovered.sessionId}`,
|
|
238
|
+
sessionId: discovered.sessionId,
|
|
239
|
+
sessionFile: discovered.sessionFile,
|
|
240
|
+
label: undefined, // No label for unnamed sessions
|
|
241
|
+
updatedAt: discovered.mtime,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
// Sort by most recent first
|
|
247
|
+
mergedEntries.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
248
|
+
// Apply limit
|
|
249
|
+
const limitedEntries = mergedEntries.slice(0, limit);
|
|
250
|
+
// Load usage for each session
|
|
251
|
+
const sessions = [];
|
|
252
|
+
const aggregateTotals = {
|
|
253
|
+
input: 0,
|
|
254
|
+
output: 0,
|
|
255
|
+
cacheRead: 0,
|
|
256
|
+
cacheWrite: 0,
|
|
257
|
+
totalTokens: 0,
|
|
258
|
+
totalCost: 0,
|
|
259
|
+
inputCost: 0,
|
|
260
|
+
outputCost: 0,
|
|
261
|
+
cacheReadCost: 0,
|
|
262
|
+
cacheWriteCost: 0,
|
|
263
|
+
missingCostEntries: 0,
|
|
264
|
+
};
|
|
265
|
+
const aggregateMessages = {
|
|
266
|
+
total: 0,
|
|
267
|
+
user: 0,
|
|
268
|
+
assistant: 0,
|
|
269
|
+
toolCalls: 0,
|
|
270
|
+
toolResults: 0,
|
|
271
|
+
errors: 0,
|
|
272
|
+
};
|
|
273
|
+
const toolAggregateMap = new Map();
|
|
274
|
+
const byModelMap = new Map();
|
|
275
|
+
const byProviderMap = new Map();
|
|
276
|
+
const byAgentMap = new Map();
|
|
277
|
+
const byChannelMap = new Map();
|
|
278
|
+
const dailyAggregateMap = new Map();
|
|
279
|
+
const latencyTotals = {
|
|
280
|
+
count: 0,
|
|
281
|
+
sum: 0,
|
|
282
|
+
min: Number.POSITIVE_INFINITY,
|
|
283
|
+
max: 0,
|
|
284
|
+
p95Max: 0,
|
|
285
|
+
};
|
|
286
|
+
const dailyLatencyMap = new Map();
|
|
287
|
+
const modelDailyMap = new Map();
|
|
288
|
+
const emptyTotals = () => ({
|
|
289
|
+
input: 0,
|
|
290
|
+
output: 0,
|
|
291
|
+
cacheRead: 0,
|
|
292
|
+
cacheWrite: 0,
|
|
293
|
+
totalTokens: 0,
|
|
294
|
+
totalCost: 0,
|
|
295
|
+
inputCost: 0,
|
|
296
|
+
outputCost: 0,
|
|
297
|
+
cacheReadCost: 0,
|
|
298
|
+
cacheWriteCost: 0,
|
|
299
|
+
missingCostEntries: 0,
|
|
300
|
+
});
|
|
301
|
+
const mergeTotals = (target, source) => {
|
|
302
|
+
target.input += source.input;
|
|
303
|
+
target.output += source.output;
|
|
304
|
+
target.cacheRead += source.cacheRead;
|
|
305
|
+
target.cacheWrite += source.cacheWrite;
|
|
306
|
+
target.totalTokens += source.totalTokens;
|
|
307
|
+
target.totalCost += source.totalCost;
|
|
308
|
+
target.inputCost += source.inputCost;
|
|
309
|
+
target.outputCost += source.outputCost;
|
|
310
|
+
target.cacheReadCost += source.cacheReadCost;
|
|
311
|
+
target.cacheWriteCost += source.cacheWriteCost;
|
|
312
|
+
target.missingCostEntries += source.missingCostEntries;
|
|
313
|
+
};
|
|
314
|
+
for (const merged of limitedEntries) {
|
|
315
|
+
const usage = await loadSessionCostSummary({
|
|
316
|
+
sessionId: merged.sessionId,
|
|
317
|
+
sessionEntry: merged.storeEntry,
|
|
318
|
+
sessionFile: merged.sessionFile,
|
|
319
|
+
config,
|
|
320
|
+
startMs,
|
|
321
|
+
endMs,
|
|
322
|
+
});
|
|
323
|
+
if (usage) {
|
|
324
|
+
aggregateTotals.input += usage.input;
|
|
325
|
+
aggregateTotals.output += usage.output;
|
|
326
|
+
aggregateTotals.cacheRead += usage.cacheRead;
|
|
327
|
+
aggregateTotals.cacheWrite += usage.cacheWrite;
|
|
328
|
+
aggregateTotals.totalTokens += usage.totalTokens;
|
|
329
|
+
aggregateTotals.totalCost += usage.totalCost;
|
|
330
|
+
aggregateTotals.inputCost += usage.inputCost;
|
|
331
|
+
aggregateTotals.outputCost += usage.outputCost;
|
|
332
|
+
aggregateTotals.cacheReadCost += usage.cacheReadCost;
|
|
333
|
+
aggregateTotals.cacheWriteCost += usage.cacheWriteCost;
|
|
334
|
+
aggregateTotals.missingCostEntries += usage.missingCostEntries;
|
|
335
|
+
}
|
|
336
|
+
const agentId = parseAgentSessionKey(merged.key)?.agentId;
|
|
337
|
+
const channel = merged.storeEntry?.channel ?? merged.storeEntry?.origin?.provider;
|
|
338
|
+
const chatType = merged.storeEntry?.chatType ?? merged.storeEntry?.origin?.chatType;
|
|
339
|
+
if (usage) {
|
|
340
|
+
if (usage.messageCounts) {
|
|
341
|
+
aggregateMessages.total += usage.messageCounts.total;
|
|
342
|
+
aggregateMessages.user += usage.messageCounts.user;
|
|
343
|
+
aggregateMessages.assistant += usage.messageCounts.assistant;
|
|
344
|
+
aggregateMessages.toolCalls += usage.messageCounts.toolCalls;
|
|
345
|
+
aggregateMessages.toolResults += usage.messageCounts.toolResults;
|
|
346
|
+
aggregateMessages.errors += usage.messageCounts.errors;
|
|
347
|
+
}
|
|
348
|
+
if (usage.toolUsage) {
|
|
349
|
+
for (const tool of usage.toolUsage.tools) {
|
|
350
|
+
toolAggregateMap.set(tool.name, (toolAggregateMap.get(tool.name) ?? 0) + tool.count);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if (usage.modelUsage) {
|
|
354
|
+
for (const entry of usage.modelUsage) {
|
|
355
|
+
const modelKey = `${entry.provider ?? "unknown"}::${entry.model ?? "unknown"}`;
|
|
356
|
+
const modelExisting = byModelMap.get(modelKey) ??
|
|
357
|
+
{
|
|
358
|
+
provider: entry.provider,
|
|
359
|
+
model: entry.model,
|
|
360
|
+
count: 0,
|
|
361
|
+
totals: emptyTotals(),
|
|
362
|
+
};
|
|
363
|
+
modelExisting.count += entry.count;
|
|
364
|
+
mergeTotals(modelExisting.totals, entry.totals);
|
|
365
|
+
byModelMap.set(modelKey, modelExisting);
|
|
366
|
+
const providerKey = entry.provider ?? "unknown";
|
|
367
|
+
const providerExisting = byProviderMap.get(providerKey) ??
|
|
368
|
+
{
|
|
369
|
+
provider: entry.provider,
|
|
370
|
+
model: undefined,
|
|
371
|
+
count: 0,
|
|
372
|
+
totals: emptyTotals(),
|
|
373
|
+
};
|
|
374
|
+
providerExisting.count += entry.count;
|
|
375
|
+
mergeTotals(providerExisting.totals, entry.totals);
|
|
376
|
+
byProviderMap.set(providerKey, providerExisting);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
if (usage.latency) {
|
|
380
|
+
const { count, avgMs, minMs, maxMs, p95Ms } = usage.latency;
|
|
381
|
+
if (count > 0) {
|
|
382
|
+
latencyTotals.count += count;
|
|
383
|
+
latencyTotals.sum += avgMs * count;
|
|
384
|
+
latencyTotals.min = Math.min(latencyTotals.min, minMs);
|
|
385
|
+
latencyTotals.max = Math.max(latencyTotals.max, maxMs);
|
|
386
|
+
latencyTotals.p95Max = Math.max(latencyTotals.p95Max, p95Ms);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (usage.dailyLatency) {
|
|
390
|
+
for (const day of usage.dailyLatency) {
|
|
391
|
+
const existing = dailyLatencyMap.get(day.date) ?? {
|
|
392
|
+
date: day.date,
|
|
393
|
+
count: 0,
|
|
394
|
+
sum: 0,
|
|
395
|
+
min: Number.POSITIVE_INFINITY,
|
|
396
|
+
max: 0,
|
|
397
|
+
p95Max: 0,
|
|
398
|
+
};
|
|
399
|
+
existing.count += day.count;
|
|
400
|
+
existing.sum += day.avgMs * day.count;
|
|
401
|
+
existing.min = Math.min(existing.min, day.minMs);
|
|
402
|
+
existing.max = Math.max(existing.max, day.maxMs);
|
|
403
|
+
existing.p95Max = Math.max(existing.p95Max, day.p95Ms);
|
|
404
|
+
dailyLatencyMap.set(day.date, existing);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (usage.dailyModelUsage) {
|
|
408
|
+
for (const entry of usage.dailyModelUsage) {
|
|
409
|
+
const key = `${entry.date}::${entry.provider ?? "unknown"}::${entry.model ?? "unknown"}`;
|
|
410
|
+
const existing = modelDailyMap.get(key) ??
|
|
411
|
+
{
|
|
412
|
+
date: entry.date,
|
|
413
|
+
provider: entry.provider,
|
|
414
|
+
model: entry.model,
|
|
415
|
+
tokens: 0,
|
|
416
|
+
cost: 0,
|
|
417
|
+
count: 0,
|
|
418
|
+
};
|
|
419
|
+
existing.tokens += entry.tokens;
|
|
420
|
+
existing.cost += entry.cost;
|
|
421
|
+
existing.count += entry.count;
|
|
422
|
+
modelDailyMap.set(key, existing);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
if (agentId) {
|
|
426
|
+
const agentTotals = byAgentMap.get(agentId) ?? emptyTotals();
|
|
427
|
+
mergeTotals(agentTotals, usage);
|
|
428
|
+
byAgentMap.set(agentId, agentTotals);
|
|
429
|
+
}
|
|
430
|
+
if (channel) {
|
|
431
|
+
const channelTotals = byChannelMap.get(channel) ?? emptyTotals();
|
|
432
|
+
mergeTotals(channelTotals, usage);
|
|
433
|
+
byChannelMap.set(channel, channelTotals);
|
|
434
|
+
}
|
|
435
|
+
if (usage.dailyBreakdown) {
|
|
436
|
+
for (const day of usage.dailyBreakdown) {
|
|
437
|
+
const daily = dailyAggregateMap.get(day.date) ?? {
|
|
438
|
+
date: day.date,
|
|
439
|
+
tokens: 0,
|
|
440
|
+
cost: 0,
|
|
441
|
+
messages: 0,
|
|
442
|
+
toolCalls: 0,
|
|
443
|
+
errors: 0,
|
|
444
|
+
};
|
|
445
|
+
daily.tokens += day.tokens;
|
|
446
|
+
daily.cost += day.cost;
|
|
447
|
+
dailyAggregateMap.set(day.date, daily);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
if (usage.dailyMessageCounts) {
|
|
451
|
+
for (const day of usage.dailyMessageCounts) {
|
|
452
|
+
const daily = dailyAggregateMap.get(day.date) ?? {
|
|
453
|
+
date: day.date,
|
|
454
|
+
tokens: 0,
|
|
455
|
+
cost: 0,
|
|
456
|
+
messages: 0,
|
|
457
|
+
toolCalls: 0,
|
|
458
|
+
errors: 0,
|
|
459
|
+
};
|
|
460
|
+
daily.messages += day.total;
|
|
461
|
+
daily.toolCalls += day.toolCalls;
|
|
462
|
+
daily.errors += day.errors;
|
|
463
|
+
dailyAggregateMap.set(day.date, daily);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
sessions.push({
|
|
468
|
+
key: merged.key,
|
|
469
|
+
label: merged.label,
|
|
470
|
+
sessionId: merged.sessionId,
|
|
471
|
+
updatedAt: merged.updatedAt,
|
|
472
|
+
agentId,
|
|
473
|
+
channel,
|
|
474
|
+
chatType,
|
|
475
|
+
origin: merged.storeEntry?.origin,
|
|
476
|
+
modelOverride: merged.storeEntry?.modelOverride,
|
|
477
|
+
providerOverride: merged.storeEntry?.providerOverride,
|
|
478
|
+
modelProvider: merged.storeEntry?.modelProvider,
|
|
479
|
+
model: merged.storeEntry?.model,
|
|
480
|
+
usage,
|
|
481
|
+
contextWeight: includeContextWeight
|
|
482
|
+
? (merged.storeEntry?.systemPromptReport ?? null)
|
|
483
|
+
: undefined,
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
// Format dates back to YYYY-MM-DD strings
|
|
487
|
+
const formatDateStr = (ms) => {
|
|
488
|
+
const d = new Date(ms);
|
|
489
|
+
return `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, "0")}-${String(d.getUTCDate()).padStart(2, "0")}`;
|
|
490
|
+
};
|
|
491
|
+
const aggregates = {
|
|
492
|
+
messages: aggregateMessages,
|
|
493
|
+
tools: {
|
|
494
|
+
totalCalls: Array.from(toolAggregateMap.values()).reduce((sum, count) => sum + count, 0),
|
|
495
|
+
uniqueTools: toolAggregateMap.size,
|
|
496
|
+
tools: Array.from(toolAggregateMap.entries())
|
|
497
|
+
.map(([name, count]) => ({ name, count }))
|
|
498
|
+
.toSorted((a, b) => b.count - a.count),
|
|
499
|
+
},
|
|
500
|
+
byModel: Array.from(byModelMap.values()).toSorted((a, b) => {
|
|
501
|
+
const costDiff = b.totals.totalCost - a.totals.totalCost;
|
|
502
|
+
if (costDiff !== 0) {
|
|
503
|
+
return costDiff;
|
|
504
|
+
}
|
|
505
|
+
return b.totals.totalTokens - a.totals.totalTokens;
|
|
506
|
+
}),
|
|
507
|
+
byProvider: Array.from(byProviderMap.values()).toSorted((a, b) => {
|
|
508
|
+
const costDiff = b.totals.totalCost - a.totals.totalCost;
|
|
509
|
+
if (costDiff !== 0) {
|
|
510
|
+
return costDiff;
|
|
511
|
+
}
|
|
512
|
+
return b.totals.totalTokens - a.totals.totalTokens;
|
|
513
|
+
}),
|
|
514
|
+
byAgent: Array.from(byAgentMap.entries())
|
|
515
|
+
.map(([id, totals]) => ({ agentId: id, totals }))
|
|
516
|
+
.toSorted((a, b) => b.totals.totalCost - a.totals.totalCost),
|
|
517
|
+
byChannel: Array.from(byChannelMap.entries())
|
|
518
|
+
.map(([name, totals]) => ({ channel: name, totals }))
|
|
519
|
+
.toSorted((a, b) => b.totals.totalCost - a.totals.totalCost),
|
|
520
|
+
latency: latencyTotals.count > 0
|
|
521
|
+
? {
|
|
522
|
+
count: latencyTotals.count,
|
|
523
|
+
avgMs: latencyTotals.sum / latencyTotals.count,
|
|
524
|
+
minMs: latencyTotals.min === Number.POSITIVE_INFINITY ? 0 : latencyTotals.min,
|
|
525
|
+
maxMs: latencyTotals.max,
|
|
526
|
+
p95Ms: latencyTotals.p95Max,
|
|
527
|
+
}
|
|
528
|
+
: undefined,
|
|
529
|
+
dailyLatency: Array.from(dailyLatencyMap.values())
|
|
530
|
+
.map((entry) => ({
|
|
531
|
+
date: entry.date,
|
|
532
|
+
count: entry.count,
|
|
533
|
+
avgMs: entry.count ? entry.sum / entry.count : 0,
|
|
534
|
+
minMs: entry.min === Number.POSITIVE_INFINITY ? 0 : entry.min,
|
|
535
|
+
maxMs: entry.max,
|
|
536
|
+
p95Ms: entry.p95Max,
|
|
537
|
+
}))
|
|
538
|
+
.toSorted((a, b) => a.date.localeCompare(b.date)),
|
|
539
|
+
modelDaily: Array.from(modelDailyMap.values()).toSorted((a, b) => a.date.localeCompare(b.date) || b.cost - a.cost),
|
|
540
|
+
daily: Array.from(dailyAggregateMap.values()).toSorted((a, b) => a.date.localeCompare(b.date)),
|
|
541
|
+
};
|
|
542
|
+
const result = {
|
|
543
|
+
updatedAt: now,
|
|
544
|
+
startDate: formatDateStr(startMs),
|
|
545
|
+
endDate: formatDateStr(endMs),
|
|
546
|
+
sessions,
|
|
547
|
+
totals: aggregateTotals,
|
|
548
|
+
aggregates,
|
|
549
|
+
};
|
|
550
|
+
respond(true, result, undefined);
|
|
551
|
+
},
|
|
552
|
+
"sessions.usage.timeseries": async ({ respond, params }) => {
|
|
553
|
+
const key = typeof params?.key === "string" ? params.key.trim() : null;
|
|
554
|
+
if (!key) {
|
|
555
|
+
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "key is required for timeseries"));
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
const config = loadConfig();
|
|
559
|
+
const { entry } = loadSessionEntry(key);
|
|
560
|
+
// For discovered sessions (not in store), try using key as sessionId directly
|
|
561
|
+
const parsed = parseAgentSessionKey(key);
|
|
562
|
+
const agentId = parsed?.agentId;
|
|
563
|
+
const rawSessionId = parsed?.rest ?? key;
|
|
564
|
+
const sessionId = entry?.sessionId ?? rawSessionId;
|
|
565
|
+
const sessionFile = entry?.sessionFile ?? resolveSessionFilePath(rawSessionId, entry, { agentId });
|
|
566
|
+
const timeseries = await loadSessionUsageTimeSeries({
|
|
567
|
+
sessionId,
|
|
568
|
+
sessionEntry: entry,
|
|
569
|
+
sessionFile,
|
|
570
|
+
config,
|
|
571
|
+
maxPoints: 200,
|
|
572
|
+
});
|
|
573
|
+
if (!timeseries) {
|
|
574
|
+
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, `No transcript found for session: ${key}`));
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
respond(true, timeseries, undefined);
|
|
578
|
+
},
|
|
579
|
+
"sessions.usage.logs": async ({ respond, params }) => {
|
|
580
|
+
const key = typeof params?.key === "string" ? params.key.trim() : null;
|
|
581
|
+
if (!key) {
|
|
582
|
+
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "key is required for logs"));
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
const limit = typeof params?.limit === "number" && Number.isFinite(params.limit)
|
|
586
|
+
? Math.min(params.limit, 1000)
|
|
587
|
+
: 200;
|
|
588
|
+
const config = loadConfig();
|
|
589
|
+
const { entry } = loadSessionEntry(key);
|
|
590
|
+
// For discovered sessions (not in store), try using key as sessionId directly
|
|
591
|
+
const parsed = parseAgentSessionKey(key);
|
|
592
|
+
const agentId = parsed?.agentId;
|
|
593
|
+
const rawSessionId = parsed?.rest ?? key;
|
|
594
|
+
const sessionId = entry?.sessionId ?? rawSessionId;
|
|
595
|
+
const sessionFile = entry?.sessionFile ?? resolveSessionFilePath(rawSessionId, entry, { agentId });
|
|
596
|
+
const { loadSessionLogs } = await import("../../infra/session-cost-usage.js");
|
|
597
|
+
const logs = await loadSessionLogs({
|
|
598
|
+
sessionId,
|
|
599
|
+
sessionEntry: entry,
|
|
600
|
+
sessionFile,
|
|
601
|
+
config,
|
|
602
|
+
limit,
|
|
603
|
+
});
|
|
604
|
+
respond(true, { logs: logs ?? [] }, undefined);
|
|
605
|
+
},
|
|
63
606
|
};
|