@poolzin/pool-bot 2026.2.0 → 2026.2.2
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/CHANGELOG.md +118 -0
- package/README-header.png +0 -0
- package/dist/agents/bash-tools.exec.js +76 -25
- package/dist/agents/cli-runner/helpers.js +9 -11
- package/dist/agents/context.js +1 -1
- package/dist/agents/identity.js +47 -7
- package/dist/agents/memory-search.js +25 -8
- package/dist/agents/model-catalog.js +1 -1
- 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 +8 -10
- package/dist/agents/pi-embedded-runner/model.js +62 -3
- 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/image-tool.js +1 -1
- 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 +74 -82
- 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/mentions.js +1 -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/canvas-host/a2ui/index.html +28 -28
- 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/auth-choice.apply.oauth.js +1 -1
- 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.registry.js +1 -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/compat/legacy-names.js +2 -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/control-ui/assets/{index-CIRDm-Lu.css → index-CSfXd2LO.css} +1 -1
- package/dist/control-ui/assets/{index-CmNMuoem.js → index-HRr1grwl.js} +446 -413
- package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -0
- package/dist/control-ui/index.html +4 -4
- 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 +172 -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.mocks.js +11 -7
- 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/store.js +2 -0
- 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 +3 -3
- 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 +54 -17
- 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/dist/wizard/clack-prompter.js +9 -6
- package/extensions/googlechat/node_modules/.bin/poolbot +21 -0
- package/extensions/googlechat/package.json +2 -2
- package/extensions/line/node_modules/.bin/poolbot +21 -0
- package/extensions/line/package.json +1 -1
- package/extensions/matrix/node_modules/.bin/poolbot +21 -0
- package/extensions/matrix/package.json +1 -1
- package/extensions/memory-core/node_modules/.bin/poolbot +21 -0
- package/extensions/memory-core/package.json +4 -1
- package/extensions/twitch/node_modules/.bin/poolbot +21 -0
- package/extensions/twitch/package.json +1 -1
- package/package.json +183 -24
- package/dist/control-ui/assets/index-CmNMuoem.js.map +0 -1
|
@@ -1,11 +1,16 @@
|
|
|
1
|
+
import JSON5 from "json5";
|
|
1
2
|
import crypto from "node:crypto";
|
|
2
3
|
import fs from "node:fs";
|
|
3
4
|
import path from "node:path";
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
5
|
+
import { parseByteSize } from "../../cli/parse-bytes.js";
|
|
6
|
+
import { parseDurationMs } from "../../cli/parse-duration.js";
|
|
7
|
+
import { createSubsystemLogger } from "../../logging/subsystem.js";
|
|
6
8
|
import { deliveryContextFromSession, mergeDeliveryContext, normalizeDeliveryContext, normalizeSessionDeliveryFields, } from "../../utils/delivery-context.js";
|
|
9
|
+
import { getFileMtimeMs, isCacheEnabled, resolveCacheTtlMs } from "../cache-utils.js";
|
|
10
|
+
import { loadConfig } from "../config.js";
|
|
7
11
|
import { deriveSessionMetaPatch } from "./metadata.js";
|
|
8
12
|
import { mergeSessionEntry } from "./types.js";
|
|
13
|
+
const log = createSubsystemLogger("sessions/store");
|
|
9
14
|
const SESSION_STORE_CACHE = new Map();
|
|
10
15
|
const DEFAULT_SESSION_STORE_TTL_MS = 45_000; // 45 seconds (between 30-60s)
|
|
11
16
|
function isSessionStoreRecord(value) {
|
|
@@ -29,7 +34,14 @@ function invalidateSessionStoreCache(storePath) {
|
|
|
29
34
|
SESSION_STORE_CACHE.delete(storePath);
|
|
30
35
|
}
|
|
31
36
|
function normalizeSessionEntryDelivery(entry) {
|
|
32
|
-
const normalized = normalizeSessionDeliveryFields(
|
|
37
|
+
const normalized = normalizeSessionDeliveryFields({
|
|
38
|
+
channel: entry.channel,
|
|
39
|
+
lastChannel: entry.lastChannel,
|
|
40
|
+
lastTo: entry.lastTo,
|
|
41
|
+
lastAccountId: entry.lastAccountId,
|
|
42
|
+
lastThreadId: entry.lastThreadId ?? entry.deliveryContext?.threadId ?? entry.origin?.threadId,
|
|
43
|
+
deliveryContext: entry.deliveryContext,
|
|
44
|
+
});
|
|
33
45
|
const nextDelivery = normalized.deliveryContext;
|
|
34
46
|
const sameDelivery = (entry.deliveryContext?.channel ?? undefined) === nextDelivery?.channel &&
|
|
35
47
|
(entry.deliveryContext?.to ?? undefined) === nextDelivery?.to &&
|
|
@@ -39,8 +51,9 @@ function normalizeSessionEntryDelivery(entry) {
|
|
|
39
51
|
entry.lastTo === normalized.lastTo &&
|
|
40
52
|
entry.lastAccountId === normalized.lastAccountId &&
|
|
41
53
|
entry.lastThreadId === normalized.lastThreadId;
|
|
42
|
-
if (sameDelivery && sameLast)
|
|
54
|
+
if (sameDelivery && sameLast) {
|
|
43
55
|
return entry;
|
|
56
|
+
}
|
|
44
57
|
return {
|
|
45
58
|
...entry,
|
|
46
59
|
deliveryContext: nextDelivery,
|
|
@@ -50,10 +63,19 @@ function normalizeSessionEntryDelivery(entry) {
|
|
|
50
63
|
lastThreadId: normalized.lastThreadId,
|
|
51
64
|
};
|
|
52
65
|
}
|
|
66
|
+
function removeThreadFromDeliveryContext(context) {
|
|
67
|
+
if (!context || context.threadId == null) {
|
|
68
|
+
return context;
|
|
69
|
+
}
|
|
70
|
+
const next = { ...context };
|
|
71
|
+
delete next.threadId;
|
|
72
|
+
return next;
|
|
73
|
+
}
|
|
53
74
|
function normalizeSessionStore(store) {
|
|
54
75
|
for (const [key, entry] of Object.entries(store)) {
|
|
55
|
-
if (!entry)
|
|
76
|
+
if (!entry) {
|
|
56
77
|
continue;
|
|
78
|
+
}
|
|
57
79
|
const normalized = normalizeSessionEntryDelivery(entry);
|
|
58
80
|
if (normalized !== entry) {
|
|
59
81
|
store[key] = normalized;
|
|
@@ -92,8 +114,9 @@ export function loadSessionStore(storePath, opts = {}) {
|
|
|
92
114
|
}
|
|
93
115
|
// Best-effort migration: message provider → channel naming.
|
|
94
116
|
for (const entry of Object.values(store)) {
|
|
95
|
-
if (!entry || typeof entry !== "object")
|
|
117
|
+
if (!entry || typeof entry !== "object") {
|
|
96
118
|
continue;
|
|
119
|
+
}
|
|
97
120
|
const rec = entry;
|
|
98
121
|
if (typeof rec.channel !== "string" && typeof rec.provider === "string") {
|
|
99
122
|
rec.channel = rec.provider;
|
|
@@ -132,10 +155,233 @@ export function readSessionUpdatedAt(params) {
|
|
|
132
155
|
return undefined;
|
|
133
156
|
}
|
|
134
157
|
}
|
|
135
|
-
|
|
158
|
+
// ============================================================================
|
|
159
|
+
// Session Store Pruning, Capping & File Rotation
|
|
160
|
+
// ============================================================================
|
|
161
|
+
const DEFAULT_SESSION_PRUNE_AFTER_MS = 30 * 24 * 60 * 60 * 1000;
|
|
162
|
+
const DEFAULT_SESSION_MAX_ENTRIES = 500;
|
|
163
|
+
const DEFAULT_SESSION_ROTATE_BYTES = 10_485_760; // 10 MB
|
|
164
|
+
const DEFAULT_SESSION_MAINTENANCE_MODE = "warn";
|
|
165
|
+
function resolvePruneAfterMs(maintenance) {
|
|
166
|
+
const raw = maintenance?.pruneAfter ?? maintenance?.pruneDays;
|
|
167
|
+
if (raw === undefined || raw === null || raw === "") {
|
|
168
|
+
return DEFAULT_SESSION_PRUNE_AFTER_MS;
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
return parseDurationMs(String(raw).trim(), { defaultUnit: "d" });
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return DEFAULT_SESSION_PRUNE_AFTER_MS;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function resolveRotateBytes(maintenance) {
|
|
178
|
+
const raw = maintenance?.rotateBytes;
|
|
179
|
+
if (raw === undefined || raw === null || raw === "") {
|
|
180
|
+
return DEFAULT_SESSION_ROTATE_BYTES;
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
return parseByteSize(String(raw).trim(), { defaultUnit: "b" });
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return DEFAULT_SESSION_ROTATE_BYTES;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Resolve maintenance settings from poolbot.json (`session.maintenance`).
|
|
191
|
+
* Falls back to built-in defaults when config is missing or unset.
|
|
192
|
+
*/
|
|
193
|
+
export function resolveMaintenanceConfig() {
|
|
194
|
+
let maintenance;
|
|
195
|
+
try {
|
|
196
|
+
maintenance = loadConfig().session?.maintenance;
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
// Config may not be available (e.g. in tests). Use defaults.
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
mode: maintenance?.mode ?? DEFAULT_SESSION_MAINTENANCE_MODE,
|
|
203
|
+
pruneAfterMs: resolvePruneAfterMs(maintenance),
|
|
204
|
+
maxEntries: maintenance?.maxEntries ?? DEFAULT_SESSION_MAX_ENTRIES,
|
|
205
|
+
rotateBytes: resolveRotateBytes(maintenance),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Remove entries whose `updatedAt` is older than the configured threshold.
|
|
210
|
+
* Entries without `updatedAt` are kept (cannot determine staleness).
|
|
211
|
+
* Mutates `store` in-place.
|
|
212
|
+
*/
|
|
213
|
+
export function pruneStaleEntries(store, overrideMaxAgeMs, opts = {}) {
|
|
214
|
+
const maxAgeMs = overrideMaxAgeMs ?? resolveMaintenanceConfig().pruneAfterMs;
|
|
215
|
+
const cutoffMs = Date.now() - maxAgeMs;
|
|
216
|
+
let pruned = 0;
|
|
217
|
+
for (const [key, entry] of Object.entries(store)) {
|
|
218
|
+
if (entry?.updatedAt != null && entry.updatedAt < cutoffMs) {
|
|
219
|
+
delete store[key];
|
|
220
|
+
pruned++;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (pruned > 0 && opts.log !== false) {
|
|
224
|
+
log.info("pruned stale session entries", { pruned, maxAgeMs });
|
|
225
|
+
}
|
|
226
|
+
return pruned;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Cap the store to the N most recently updated entries.
|
|
230
|
+
* Entries without `updatedAt` are sorted last (removed first when over limit).
|
|
231
|
+
* Mutates `store` in-place.
|
|
232
|
+
*/
|
|
233
|
+
function getEntryUpdatedAt(entry) {
|
|
234
|
+
return entry?.updatedAt ?? Number.NEGATIVE_INFINITY;
|
|
235
|
+
}
|
|
236
|
+
export function getActiveSessionMaintenanceWarning(params) {
|
|
237
|
+
const activeSessionKey = params.activeSessionKey.trim();
|
|
238
|
+
if (!activeSessionKey) {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
const activeEntry = params.store[activeSessionKey];
|
|
242
|
+
if (!activeEntry) {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
const now = params.nowMs ?? Date.now();
|
|
246
|
+
const cutoffMs = now - params.pruneAfterMs;
|
|
247
|
+
const wouldPrune = activeEntry.updatedAt != null ? activeEntry.updatedAt < cutoffMs : false;
|
|
248
|
+
const keys = Object.keys(params.store);
|
|
249
|
+
const wouldCap = keys.length > params.maxEntries &&
|
|
250
|
+
keys
|
|
251
|
+
.toSorted((a, b) => getEntryUpdatedAt(params.store[b]) - getEntryUpdatedAt(params.store[a]))
|
|
252
|
+
.slice(params.maxEntries)
|
|
253
|
+
.includes(activeSessionKey);
|
|
254
|
+
if (!wouldPrune && !wouldCap) {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
return {
|
|
258
|
+
activeSessionKey,
|
|
259
|
+
activeUpdatedAt: activeEntry.updatedAt,
|
|
260
|
+
totalEntries: keys.length,
|
|
261
|
+
pruneAfterMs: params.pruneAfterMs,
|
|
262
|
+
maxEntries: params.maxEntries,
|
|
263
|
+
wouldPrune,
|
|
264
|
+
wouldCap,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
export function capEntryCount(store, overrideMax, opts = {}) {
|
|
268
|
+
const maxEntries = overrideMax ?? resolveMaintenanceConfig().maxEntries;
|
|
269
|
+
const keys = Object.keys(store);
|
|
270
|
+
if (keys.length <= maxEntries) {
|
|
271
|
+
return 0;
|
|
272
|
+
}
|
|
273
|
+
// Sort by updatedAt descending; entries without updatedAt go to the end (removed first).
|
|
274
|
+
const sorted = keys.toSorted((a, b) => {
|
|
275
|
+
const aTime = getEntryUpdatedAt(store[a]);
|
|
276
|
+
const bTime = getEntryUpdatedAt(store[b]);
|
|
277
|
+
return bTime - aTime;
|
|
278
|
+
});
|
|
279
|
+
const toRemove = sorted.slice(maxEntries);
|
|
280
|
+
for (const key of toRemove) {
|
|
281
|
+
delete store[key];
|
|
282
|
+
}
|
|
283
|
+
if (opts.log !== false) {
|
|
284
|
+
log.info("capped session entry count", { removed: toRemove.length, maxEntries });
|
|
285
|
+
}
|
|
286
|
+
return toRemove.length;
|
|
287
|
+
}
|
|
288
|
+
async function getSessionFileSize(storePath) {
|
|
289
|
+
try {
|
|
290
|
+
const stat = await fs.promises.stat(storePath);
|
|
291
|
+
return stat.size;
|
|
292
|
+
}
|
|
293
|
+
catch {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Rotate the sessions file if it exceeds the configured size threshold.
|
|
299
|
+
* Renames the current file to `sessions.json.bak.{timestamp}` and cleans up
|
|
300
|
+
* old rotation backups, keeping only the 3 most recent `.bak.*` files.
|
|
301
|
+
*/
|
|
302
|
+
export async function rotateSessionFile(storePath, overrideBytes) {
|
|
303
|
+
const maxBytes = overrideBytes ?? resolveMaintenanceConfig().rotateBytes;
|
|
304
|
+
// Check current file size (file may not exist yet).
|
|
305
|
+
const fileSize = await getSessionFileSize(storePath);
|
|
306
|
+
if (fileSize == null) {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
if (fileSize <= maxBytes) {
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
// Rotate: rename current file to .bak.{timestamp}
|
|
313
|
+
const backupPath = `${storePath}.bak.${Date.now()}`;
|
|
314
|
+
try {
|
|
315
|
+
await fs.promises.rename(storePath, backupPath);
|
|
316
|
+
log.info("rotated session store file", {
|
|
317
|
+
backupPath: path.basename(backupPath),
|
|
318
|
+
sizeBytes: fileSize,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
catch {
|
|
322
|
+
// If rename fails (e.g. file disappeared), skip rotation.
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
// Clean up old backups — keep only the 3 most recent .bak.* files.
|
|
326
|
+
try {
|
|
327
|
+
const dir = path.dirname(storePath);
|
|
328
|
+
const baseName = path.basename(storePath);
|
|
329
|
+
const files = await fs.promises.readdir(dir);
|
|
330
|
+
const backups = files
|
|
331
|
+
.filter((f) => f.startsWith(`${baseName}.bak.`))
|
|
332
|
+
.toSorted()
|
|
333
|
+
.toReversed();
|
|
334
|
+
const maxBackups = 3;
|
|
335
|
+
if (backups.length > maxBackups) {
|
|
336
|
+
const toDelete = backups.slice(maxBackups);
|
|
337
|
+
for (const old of toDelete) {
|
|
338
|
+
await fs.promises.unlink(path.join(dir, old)).catch(() => undefined);
|
|
339
|
+
}
|
|
340
|
+
log.info("cleaned up old session store backups", { deleted: toDelete.length });
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
catch {
|
|
344
|
+
// Best-effort cleanup; don't fail the write.
|
|
345
|
+
}
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
async function saveSessionStoreUnlocked(storePath, store, opts) {
|
|
136
349
|
// Invalidate cache on write to ensure consistency
|
|
137
350
|
invalidateSessionStoreCache(storePath);
|
|
138
351
|
normalizeSessionStore(store);
|
|
352
|
+
if (!opts?.skipMaintenance) {
|
|
353
|
+
// Resolve maintenance config once (avoids repeated loadConfig() calls).
|
|
354
|
+
const maintenance = resolveMaintenanceConfig();
|
|
355
|
+
const shouldWarnOnly = maintenance.mode === "warn";
|
|
356
|
+
if (shouldWarnOnly) {
|
|
357
|
+
const activeSessionKey = opts?.activeSessionKey?.trim();
|
|
358
|
+
if (activeSessionKey) {
|
|
359
|
+
const warning = getActiveSessionMaintenanceWarning({
|
|
360
|
+
store,
|
|
361
|
+
activeSessionKey,
|
|
362
|
+
pruneAfterMs: maintenance.pruneAfterMs,
|
|
363
|
+
maxEntries: maintenance.maxEntries,
|
|
364
|
+
});
|
|
365
|
+
if (warning) {
|
|
366
|
+
log.warn("session maintenance would evict active session; skipping enforcement", {
|
|
367
|
+
activeSessionKey: warning.activeSessionKey,
|
|
368
|
+
wouldPrune: warning.wouldPrune,
|
|
369
|
+
wouldCap: warning.wouldCap,
|
|
370
|
+
pruneAfterMs: warning.pruneAfterMs,
|
|
371
|
+
maxEntries: warning.maxEntries,
|
|
372
|
+
});
|
|
373
|
+
await opts?.onWarn?.(warning);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
// Prune stale entries and cap total count before serializing.
|
|
379
|
+
pruneStaleEntries(store, maintenance.pruneAfterMs);
|
|
380
|
+
capEntryCount(store, maintenance.maxEntries);
|
|
381
|
+
// Rotate the on-disk file if it exceeds the size threshold.
|
|
382
|
+
await rotateSessionFile(storePath, maintenance.rotateBytes);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
139
385
|
await fs.promises.mkdir(path.dirname(storePath), { recursive: true });
|
|
140
386
|
const json = JSON.stringify(store, null, 2);
|
|
141
387
|
// Windows: avoid atomic rename swaps (can be flaky under concurrent access).
|
|
@@ -148,8 +394,9 @@ async function saveSessionStoreUnlocked(storePath, store) {
|
|
|
148
394
|
const code = err && typeof err === "object" && "code" in err
|
|
149
395
|
? String(err.code)
|
|
150
396
|
: null;
|
|
151
|
-
if (code === "ENOENT")
|
|
397
|
+
if (code === "ENOENT") {
|
|
152
398
|
return;
|
|
399
|
+
}
|
|
153
400
|
throw err;
|
|
154
401
|
}
|
|
155
402
|
return;
|
|
@@ -177,8 +424,9 @@ async function saveSessionStoreUnlocked(storePath, store) {
|
|
|
177
424
|
const code2 = err2 && typeof err2 === "object" && "code" in err2
|
|
178
425
|
? String(err2.code)
|
|
179
426
|
: null;
|
|
180
|
-
if (code2 === "ENOENT")
|
|
427
|
+
if (code2 === "ENOENT") {
|
|
181
428
|
return;
|
|
429
|
+
}
|
|
182
430
|
throw err2;
|
|
183
431
|
}
|
|
184
432
|
return;
|
|
@@ -189,17 +437,17 @@ async function saveSessionStoreUnlocked(storePath, store) {
|
|
|
189
437
|
await fs.promises.rm(tmp, { force: true });
|
|
190
438
|
}
|
|
191
439
|
}
|
|
192
|
-
export async function saveSessionStore(storePath, store) {
|
|
440
|
+
export async function saveSessionStore(storePath, store, opts) {
|
|
193
441
|
await withSessionStoreLock(storePath, async () => {
|
|
194
|
-
await saveSessionStoreUnlocked(storePath, store);
|
|
442
|
+
await saveSessionStoreUnlocked(storePath, store, opts);
|
|
195
443
|
});
|
|
196
444
|
}
|
|
197
|
-
export async function updateSessionStore(storePath, mutator) {
|
|
445
|
+
export async function updateSessionStore(storePath, mutator, opts) {
|
|
198
446
|
return await withSessionStoreLock(storePath, async () => {
|
|
199
447
|
// Always re-read inside the lock to avoid clobbering concurrent writers.
|
|
200
448
|
const store = loadSessionStore(storePath, { skipCache: true });
|
|
201
449
|
const result = await mutator(store);
|
|
202
|
-
await saveSessionStoreUnlocked(storePath, store);
|
|
450
|
+
await saveSessionStoreUnlocked(storePath, store, opts);
|
|
203
451
|
return result;
|
|
204
452
|
});
|
|
205
453
|
}
|
|
@@ -235,11 +483,12 @@ async function withSessionStoreLock(storePath, fn, opts = {}) {
|
|
|
235
483
|
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
236
484
|
continue;
|
|
237
485
|
}
|
|
238
|
-
if (code !== "EEXIST")
|
|
486
|
+
if (code !== "EEXIST") {
|
|
239
487
|
throw err;
|
|
488
|
+
}
|
|
240
489
|
const now = Date.now();
|
|
241
490
|
if (now - startedAt > timeoutMs) {
|
|
242
|
-
throw new Error(`timeout acquiring session store lock: ${lockPath}
|
|
491
|
+
throw new Error(`timeout acquiring session store lock: ${lockPath}`, { cause: err });
|
|
243
492
|
}
|
|
244
493
|
// Best-effort stale lock eviction (e.g. crashed process).
|
|
245
494
|
try {
|
|
@@ -268,14 +517,16 @@ export async function updateSessionStoreEntry(params) {
|
|
|
268
517
|
return await withSessionStoreLock(storePath, async () => {
|
|
269
518
|
const store = loadSessionStore(storePath);
|
|
270
519
|
const existing = store[sessionKey];
|
|
271
|
-
if (!existing)
|
|
520
|
+
if (!existing) {
|
|
272
521
|
return null;
|
|
522
|
+
}
|
|
273
523
|
const patch = await update(existing);
|
|
274
|
-
if (!patch)
|
|
524
|
+
if (!patch) {
|
|
275
525
|
return existing;
|
|
526
|
+
}
|
|
276
527
|
const next = mergeSessionEntry(existing, patch);
|
|
277
528
|
store[sessionKey] = next;
|
|
278
|
-
await saveSessionStoreUnlocked(storePath, store);
|
|
529
|
+
await saveSessionStoreUnlocked(storePath, store, { activeSessionKey: sessionKey });
|
|
279
530
|
return next;
|
|
280
531
|
});
|
|
281
532
|
}
|
|
@@ -290,14 +541,16 @@ export async function recordSessionMetaFromInbound(params) {
|
|
|
290
541
|
existing,
|
|
291
542
|
groupResolution: params.groupResolution,
|
|
292
543
|
});
|
|
293
|
-
if (!patch)
|
|
544
|
+
if (!patch) {
|
|
294
545
|
return existing ?? null;
|
|
295
|
-
|
|
546
|
+
}
|
|
547
|
+
if (!existing && !createIfMissing) {
|
|
296
548
|
return null;
|
|
549
|
+
}
|
|
297
550
|
const next = mergeSessionEntry(existing, patch);
|
|
298
551
|
store[sessionKey] = next;
|
|
299
552
|
return next;
|
|
300
|
-
});
|
|
553
|
+
}, { activeSessionKey: sessionKey });
|
|
301
554
|
}
|
|
302
555
|
export async function updateLastRoute(params) {
|
|
303
556
|
const { storePath, sessionKey, channel, to, accountId, threadId, ctx } = params;
|
|
@@ -313,7 +566,22 @@ export async function updateLastRoute(params) {
|
|
|
313
566
|
threadId,
|
|
314
567
|
});
|
|
315
568
|
const mergedInput = mergeDeliveryContext(explicitContext, inlineContext);
|
|
316
|
-
const
|
|
569
|
+
const explicitDeliveryContext = params.deliveryContext;
|
|
570
|
+
const explicitThreadFromDeliveryContext = explicitDeliveryContext != null &&
|
|
571
|
+
Object.prototype.hasOwnProperty.call(explicitDeliveryContext, "threadId")
|
|
572
|
+
? explicitDeliveryContext.threadId
|
|
573
|
+
: undefined;
|
|
574
|
+
const explicitThreadValue = explicitThreadFromDeliveryContext ??
|
|
575
|
+
(threadId != null && threadId !== "" ? threadId : undefined);
|
|
576
|
+
const explicitRouteProvided = Boolean(explicitContext?.channel ||
|
|
577
|
+
explicitContext?.to ||
|
|
578
|
+
inlineContext?.channel ||
|
|
579
|
+
inlineContext?.to);
|
|
580
|
+
const clearThreadFromFallback = explicitRouteProvided && explicitThreadValue == null;
|
|
581
|
+
const fallbackContext = clearThreadFromFallback
|
|
582
|
+
? removeThreadFromDeliveryContext(deliveryContextFromSession(existing))
|
|
583
|
+
: deliveryContextFromSession(existing);
|
|
584
|
+
const merged = mergeDeliveryContext(mergedInput, fallbackContext);
|
|
317
585
|
const normalized = normalizeSessionDeliveryFields({
|
|
318
586
|
deliveryContext: {
|
|
319
587
|
channel: merged?.channel,
|
|
@@ -340,7 +608,7 @@ export async function updateLastRoute(params) {
|
|
|
340
608
|
};
|
|
341
609
|
const next = mergeSessionEntry(existing, metaPatch ? { ...basePatch, ...metaPatch } : basePatch);
|
|
342
610
|
store[sessionKey] = next;
|
|
343
|
-
await saveSessionStoreUnlocked(storePath, store);
|
|
611
|
+
await saveSessionStoreUnlocked(storePath, store, { activeSessionKey: sessionKey });
|
|
344
612
|
return next;
|
|
345
613
|
});
|
|
346
614
|
}
|
|
@@ -23,6 +23,8 @@ export const AgentDefaultsSchema = z
|
|
|
23
23
|
alias: z.string().optional(),
|
|
24
24
|
/** Provider-specific API parameters (e.g., GLM-4.7 thinking mode). */
|
|
25
25
|
params: z.record(z.string(), z.unknown()).optional(),
|
|
26
|
+
/** Enable streaming for this model (default: true, false for Ollama to avoid SDK issue #1205). */
|
|
27
|
+
streaming: z.boolean().optional(),
|
|
26
28
|
})
|
|
27
29
|
.strict())
|
|
28
30
|
.optional(),
|
|
@@ -135,6 +137,7 @@ export const AgentDefaultsSchema = z
|
|
|
135
137
|
.strict(),
|
|
136
138
|
])
|
|
137
139
|
.optional(),
|
|
140
|
+
thinking: z.string().optional(),
|
|
138
141
|
})
|
|
139
142
|
.strict()
|
|
140
143
|
.optional(),
|
|
@@ -17,6 +17,7 @@ export const HeartbeatSchema = z
|
|
|
17
17
|
includeReasoning: z.boolean().optional(),
|
|
18
18
|
target: z.string().optional(),
|
|
19
19
|
to: z.string().optional(),
|
|
20
|
+
accountId: z.string().optional(),
|
|
20
21
|
prompt: z.string().optional(),
|
|
21
22
|
ackMaxChars: z.number().int().nonnegative().optional(),
|
|
22
23
|
})
|
|
@@ -278,13 +279,16 @@ export const MemorySearchSchema = z
|
|
|
278
279
|
.object({
|
|
279
280
|
enabled: z.boolean().optional(),
|
|
280
281
|
sources: z.array(z.union([z.literal("memory"), z.literal("sessions")])).optional(),
|
|
282
|
+
extraPaths: z.array(z.string()).optional(),
|
|
281
283
|
experimental: z
|
|
282
284
|
.object({
|
|
283
285
|
sessionMemory: z.boolean().optional(),
|
|
284
286
|
})
|
|
285
287
|
.strict()
|
|
286
288
|
.optional(),
|
|
287
|
-
provider: z
|
|
289
|
+
provider: z
|
|
290
|
+
.union([z.literal("openai"), z.literal("local"), z.literal("gemini"), z.literal("voyage")])
|
|
291
|
+
.optional(),
|
|
288
292
|
remote: z
|
|
289
293
|
.object({
|
|
290
294
|
baseUrl: z.string().optional(),
|
|
@@ -304,7 +308,13 @@ export const MemorySearchSchema = z
|
|
|
304
308
|
.strict()
|
|
305
309
|
.optional(),
|
|
306
310
|
fallback: z
|
|
307
|
-
.union([
|
|
311
|
+
.union([
|
|
312
|
+
z.literal("openai"),
|
|
313
|
+
z.literal("gemini"),
|
|
314
|
+
z.literal("local"),
|
|
315
|
+
z.literal("voyage"),
|
|
316
|
+
z.literal("none"),
|
|
317
|
+
])
|
|
308
318
|
.optional(),
|
|
309
319
|
model: z.string().optional(),
|
|
310
320
|
local: z
|
|
@@ -414,6 +424,7 @@ export const AgentEntrySchema = z
|
|
|
414
424
|
.strict(),
|
|
415
425
|
])
|
|
416
426
|
.optional(),
|
|
427
|
+
thinking: z.string().optional(),
|
|
417
428
|
})
|
|
418
429
|
.strict()
|
|
419
430
|
.optional(),
|