@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,9 +1,10 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
|
-
import { normalizeCronJobCreate, normalizeCronJobPatch } from "../../cron/normalize.js";
|
|
3
2
|
import { loadConfig } from "../../config/config.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { normalizeCronJobCreate, normalizeCronJobPatch } from "../../cron/normalize.js";
|
|
4
|
+
import { parseAgentSessionKey } from "../../sessions/session-key-utils.js";
|
|
5
|
+
import { isRecord, truncateUtf16Safe } from "../../utils.js";
|
|
6
6
|
import { resolveSessionAgentId } from "../agent-scope.js";
|
|
7
|
+
import { optionalStringEnum, stringEnum } from "../schema/typebox.js";
|
|
7
8
|
import { jsonResult, readStringParam } from "./common.js";
|
|
8
9
|
import { callGatewayTool } from "./gateway.js";
|
|
9
10
|
import { resolveInternalSessionKey, resolveMainSessionAlias } from "./sessions-helpers.js";
|
|
@@ -13,6 +14,7 @@ import { resolveInternalSessionKey, resolveMainSessionAlias } from "./sessions-h
|
|
|
13
14
|
// accept "any object" here and validate at runtime.
|
|
14
15
|
const CRON_ACTIONS = ["status", "list", "add", "update", "remove", "run", "runs", "wake"];
|
|
15
16
|
const CRON_WAKE_MODES = ["now", "next-heartbeat"];
|
|
17
|
+
const CRON_RUN_MODES = ["due", "force"];
|
|
16
18
|
const REMINDER_CONTEXT_MESSAGES_MAX = 10;
|
|
17
19
|
const REMINDER_CONTEXT_PER_MESSAGE_MAX = 220;
|
|
18
20
|
const REMINDER_CONTEXT_TOTAL_MAX = 700;
|
|
@@ -30,17 +32,20 @@ const CronToolSchema = Type.Object({
|
|
|
30
32
|
patch: Type.Optional(Type.Object({}, { additionalProperties: true })),
|
|
31
33
|
text: Type.Optional(Type.String()),
|
|
32
34
|
mode: optionalStringEnum(CRON_WAKE_MODES),
|
|
35
|
+
runMode: optionalStringEnum(CRON_RUN_MODES),
|
|
33
36
|
contextMessages: Type.Optional(Type.Number({ minimum: 0, maximum: REMINDER_CONTEXT_MESSAGES_MAX })),
|
|
34
37
|
});
|
|
35
38
|
function stripExistingContext(text) {
|
|
36
39
|
const index = text.indexOf(REMINDER_CONTEXT_MARKER);
|
|
37
|
-
if (index === -1)
|
|
40
|
+
if (index === -1) {
|
|
38
41
|
return text;
|
|
42
|
+
}
|
|
39
43
|
return text.slice(0, index).trim();
|
|
40
44
|
}
|
|
41
45
|
function truncateText(input, maxLen) {
|
|
42
|
-
if (input.length <= maxLen)
|
|
46
|
+
if (input.length <= maxLen) {
|
|
43
47
|
return input;
|
|
48
|
+
}
|
|
44
49
|
const truncated = truncateUtf16Safe(input, Math.max(0, maxLen - 3)).trimEnd();
|
|
45
50
|
return `${truncated}...`;
|
|
46
51
|
}
|
|
@@ -49,21 +54,25 @@ function normalizeContextText(raw) {
|
|
|
49
54
|
}
|
|
50
55
|
function extractMessageText(message) {
|
|
51
56
|
const role = typeof message.role === "string" ? message.role : "";
|
|
52
|
-
if (role !== "user" && role !== "assistant")
|
|
57
|
+
if (role !== "user" && role !== "assistant") {
|
|
53
58
|
return null;
|
|
59
|
+
}
|
|
54
60
|
const content = message.content;
|
|
55
61
|
if (typeof content === "string") {
|
|
56
62
|
const normalized = normalizeContextText(content);
|
|
57
63
|
return normalized ? { role, text: normalized } : null;
|
|
58
64
|
}
|
|
59
|
-
if (!Array.isArray(content))
|
|
65
|
+
if (!Array.isArray(content)) {
|
|
60
66
|
return null;
|
|
67
|
+
}
|
|
61
68
|
const chunks = [];
|
|
62
69
|
for (const block of content) {
|
|
63
|
-
if (!block || typeof block !== "object")
|
|
70
|
+
if (!block || typeof block !== "object") {
|
|
64
71
|
continue;
|
|
65
|
-
|
|
72
|
+
}
|
|
73
|
+
if (block.type !== "text") {
|
|
66
74
|
continue;
|
|
75
|
+
}
|
|
67
76
|
const text = block.text;
|
|
68
77
|
if (typeof text === "string" && text.trim()) {
|
|
69
78
|
chunks.push(text);
|
|
@@ -74,26 +83,29 @@ function extractMessageText(message) {
|
|
|
74
83
|
}
|
|
75
84
|
async function buildReminderContextLines(params) {
|
|
76
85
|
const maxMessages = Math.min(REMINDER_CONTEXT_MESSAGES_MAX, Math.max(0, Math.floor(params.contextMessages)));
|
|
77
|
-
if (maxMessages <= 0)
|
|
86
|
+
if (maxMessages <= 0) {
|
|
78
87
|
return [];
|
|
88
|
+
}
|
|
79
89
|
const sessionKey = params.agentSessionKey?.trim();
|
|
80
|
-
if (!sessionKey)
|
|
90
|
+
if (!sessionKey) {
|
|
81
91
|
return [];
|
|
92
|
+
}
|
|
82
93
|
const cfg = loadConfig();
|
|
83
94
|
const { mainKey, alias } = resolveMainSessionAlias(cfg);
|
|
84
95
|
const resolvedKey = resolveInternalSessionKey({ key: sessionKey, alias, mainKey });
|
|
85
96
|
try {
|
|
86
|
-
const res =
|
|
97
|
+
const res = await callGatewayTool("chat.history", params.gatewayOpts, {
|
|
87
98
|
sessionKey: resolvedKey,
|
|
88
99
|
limit: maxMessages,
|
|
89
|
-
})
|
|
100
|
+
});
|
|
90
101
|
const messages = Array.isArray(res?.messages) ? res.messages : [];
|
|
91
102
|
const parsed = messages
|
|
92
103
|
.map((msg) => extractMessageText(msg))
|
|
93
104
|
.filter((msg) => Boolean(msg));
|
|
94
105
|
const recent = parsed.slice(-maxMessages);
|
|
95
|
-
if (recent.length === 0)
|
|
106
|
+
if (recent.length === 0) {
|
|
96
107
|
return [];
|
|
108
|
+
}
|
|
97
109
|
const lines = [];
|
|
98
110
|
let total = 0;
|
|
99
111
|
for (const entry of recent) {
|
|
@@ -101,8 +113,9 @@ async function buildReminderContextLines(params) {
|
|
|
101
113
|
const text = truncateText(entry.text, REMINDER_CONTEXT_PER_MESSAGE_MAX);
|
|
102
114
|
const line = `- ${label}: ${text}`;
|
|
103
115
|
total += line.length;
|
|
104
|
-
if (total > REMINDER_CONTEXT_TOTAL_MAX)
|
|
116
|
+
if (total > REMINDER_CONTEXT_TOTAL_MAX) {
|
|
105
117
|
break;
|
|
118
|
+
}
|
|
106
119
|
lines.push(line);
|
|
107
120
|
}
|
|
108
121
|
return lines;
|
|
@@ -111,6 +124,62 @@ async function buildReminderContextLines(params) {
|
|
|
111
124
|
return [];
|
|
112
125
|
}
|
|
113
126
|
}
|
|
127
|
+
function stripThreadSuffixFromSessionKey(sessionKey) {
|
|
128
|
+
const normalized = sessionKey.toLowerCase();
|
|
129
|
+
const idx = normalized.lastIndexOf(":thread:");
|
|
130
|
+
if (idx <= 0) {
|
|
131
|
+
return sessionKey;
|
|
132
|
+
}
|
|
133
|
+
const parent = sessionKey.slice(0, idx).trim();
|
|
134
|
+
return parent ? parent : sessionKey;
|
|
135
|
+
}
|
|
136
|
+
function inferDeliveryFromSessionKey(agentSessionKey) {
|
|
137
|
+
const rawSessionKey = agentSessionKey?.trim();
|
|
138
|
+
if (!rawSessionKey) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
const parsed = parseAgentSessionKey(stripThreadSuffixFromSessionKey(rawSessionKey));
|
|
142
|
+
if (!parsed || !parsed.rest) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
const parts = parsed.rest.split(":").filter(Boolean);
|
|
146
|
+
if (parts.length === 0) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
const head = parts[0]?.trim().toLowerCase();
|
|
150
|
+
if (!head || head === "main" || head === "subagent" || head === "acp") {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
// buildAgentPeerSessionKey encodes peers as:
|
|
154
|
+
// - direct:<peerId>
|
|
155
|
+
// - <channel>:direct:<peerId>
|
|
156
|
+
// - <channel>:<accountId>:direct:<peerId>
|
|
157
|
+
// - <channel>:group:<peerId>
|
|
158
|
+
// - <channel>:channel:<peerId>
|
|
159
|
+
// Note: legacy keys may use "dm" instead of "direct".
|
|
160
|
+
// Threaded sessions append :thread:<id>, which we strip so delivery targets the parent peer.
|
|
161
|
+
// NOTE: Telegram forum topics encode as <chatId>:topic:<topicId> and should be preserved.
|
|
162
|
+
const markerIndex = parts.findIndex((part) => part === "direct" || part === "dm" || part === "group" || part === "channel");
|
|
163
|
+
if (markerIndex === -1) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
const peerId = parts
|
|
167
|
+
.slice(markerIndex + 1)
|
|
168
|
+
.join(":")
|
|
169
|
+
.trim();
|
|
170
|
+
if (!peerId) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
let channel;
|
|
174
|
+
if (markerIndex >= 1) {
|
|
175
|
+
channel = parts[0]?.trim().toLowerCase();
|
|
176
|
+
}
|
|
177
|
+
const delivery = { mode: "announce", to: peerId };
|
|
178
|
+
if (channel) {
|
|
179
|
+
delivery.channel = channel;
|
|
180
|
+
}
|
|
181
|
+
return delivery;
|
|
182
|
+
}
|
|
114
183
|
export function createCronTool(opts) {
|
|
115
184
|
return {
|
|
116
185
|
label: "Cron",
|
|
@@ -132,27 +201,36 @@ JOB SCHEMA (for add action):
|
|
|
132
201
|
"name": "string (optional)",
|
|
133
202
|
"schedule": { ... }, // Required: when to run
|
|
134
203
|
"payload": { ... }, // Required: what to execute
|
|
204
|
+
"delivery": { ... }, // Optional: announce summary (isolated only)
|
|
135
205
|
"sessionTarget": "main" | "isolated", // Required
|
|
136
206
|
"enabled": true | false // Optional, default true
|
|
137
207
|
}
|
|
138
208
|
|
|
139
209
|
SCHEDULE TYPES (schedule.kind):
|
|
140
210
|
- "at": One-shot at absolute time
|
|
141
|
-
{ "kind": "at", "
|
|
211
|
+
{ "kind": "at", "at": "<ISO-8601 timestamp>" }
|
|
142
212
|
- "every": Recurring interval
|
|
143
213
|
{ "kind": "every", "everyMs": <interval-ms>, "anchorMs": <optional-start-ms> }
|
|
144
214
|
- "cron": Cron expression
|
|
145
215
|
{ "kind": "cron", "expr": "<cron-expression>", "tz": "<optional-timezone>" }
|
|
146
216
|
|
|
217
|
+
ISO timestamps without an explicit timezone are treated as UTC.
|
|
218
|
+
|
|
147
219
|
PAYLOAD TYPES (payload.kind):
|
|
148
220
|
- "systemEvent": Injects text as system event into session
|
|
149
221
|
{ "kind": "systemEvent", "text": "<message>" }
|
|
150
222
|
- "agentTurn": Runs agent with message (isolated sessions only)
|
|
151
|
-
{ "kind": "agentTurn", "message": "<prompt>", "model": "<optional>", "thinking": "<optional>", "timeoutSeconds": <optional
|
|
223
|
+
{ "kind": "agentTurn", "message": "<prompt>", "model": "<optional>", "thinking": "<optional>", "timeoutSeconds": <optional> }
|
|
224
|
+
|
|
225
|
+
DELIVERY (isolated-only, top-level):
|
|
226
|
+
{ "mode": "none|announce", "channel": "<optional>", "to": "<optional>", "bestEffort": <optional-bool> }
|
|
227
|
+
- Default for isolated agentTurn jobs (when delivery omitted): "announce"
|
|
228
|
+
- If the task needs to send to a specific chat/recipient, set delivery.channel/to here; do not call messaging tools inside the run.
|
|
152
229
|
|
|
153
230
|
CRITICAL CONSTRAINTS:
|
|
154
231
|
- sessionTarget="main" REQUIRES payload.kind="systemEvent"
|
|
155
232
|
- sessionTarget="isolated" REQUIRES payload.kind="agentTurn"
|
|
233
|
+
Default: prefer isolated agentTurn jobs unless the user explicitly wants a main-session system event.
|
|
156
234
|
|
|
157
235
|
WAKE MODES (for wake action):
|
|
158
236
|
- "next-heartbeat" (default): Wake on next heartbeat
|
|
@@ -166,7 +244,7 @@ Use jobId as the canonical identifier; id is accepted for compatibility. Use con
|
|
|
166
244
|
const gatewayOpts = {
|
|
167
245
|
gatewayUrl: readStringParam(params, "gatewayUrl", { trim: false }),
|
|
168
246
|
gatewayToken: readStringParam(params, "gatewayToken", { trim: false }),
|
|
169
|
-
timeoutMs: typeof params.timeoutMs === "number" ? params.timeoutMs :
|
|
247
|
+
timeoutMs: typeof params.timeoutMs === "number" ? params.timeoutMs : 60_000,
|
|
170
248
|
};
|
|
171
249
|
switch (action) {
|
|
172
250
|
case "status":
|
|
@@ -176,6 +254,52 @@ Use jobId as the canonical identifier; id is accepted for compatibility. Use con
|
|
|
176
254
|
includeDisabled: Boolean(params.includeDisabled),
|
|
177
255
|
}));
|
|
178
256
|
case "add": {
|
|
257
|
+
// Flat-params recovery: non-frontier models (e.g. Grok) sometimes flatten
|
|
258
|
+
// job properties to the top level alongside `action` instead of nesting
|
|
259
|
+
// them inside `job`. When `params.job` is missing or empty, reconstruct
|
|
260
|
+
// a synthetic job object from any recognised top-level job fields.
|
|
261
|
+
// See: https://github.com/openclaw/openclaw/issues/11310
|
|
262
|
+
if (!params.job ||
|
|
263
|
+
(typeof params.job === "object" &&
|
|
264
|
+
params.job !== null &&
|
|
265
|
+
Object.keys(params.job).length === 0)) {
|
|
266
|
+
const JOB_KEYS = new Set([
|
|
267
|
+
"name",
|
|
268
|
+
"schedule",
|
|
269
|
+
"sessionTarget",
|
|
270
|
+
"wakeMode",
|
|
271
|
+
"payload",
|
|
272
|
+
"delivery",
|
|
273
|
+
"enabled",
|
|
274
|
+
"description",
|
|
275
|
+
"deleteAfterRun",
|
|
276
|
+
"agentId",
|
|
277
|
+
"message",
|
|
278
|
+
"text",
|
|
279
|
+
"model",
|
|
280
|
+
"thinking",
|
|
281
|
+
"timeoutSeconds",
|
|
282
|
+
"allowUnsafeExternalContent",
|
|
283
|
+
]);
|
|
284
|
+
const synthetic = {};
|
|
285
|
+
let found = false;
|
|
286
|
+
for (const key of Object.keys(params)) {
|
|
287
|
+
if (JOB_KEYS.has(key) && params[key] !== undefined) {
|
|
288
|
+
synthetic[key] = params[key];
|
|
289
|
+
found = true;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
// Only use the synthetic job if at least one meaningful field is present
|
|
293
|
+
// (schedule, payload, message, or text are the minimum signals that the
|
|
294
|
+
// LLM intended to create a job).
|
|
295
|
+
if (found &&
|
|
296
|
+
(synthetic.schedule !== undefined ||
|
|
297
|
+
synthetic.payload !== undefined ||
|
|
298
|
+
synthetic.message !== undefined ||
|
|
299
|
+
synthetic.text !== undefined)) {
|
|
300
|
+
params.job = synthetic;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
179
303
|
if (!params.job || typeof params.job !== "object") {
|
|
180
304
|
throw new Error("job required");
|
|
181
305
|
}
|
|
@@ -189,6 +313,28 @@ Use jobId as the canonical identifier; id is accepted for compatibility. Use con
|
|
|
189
313
|
job.agentId = agentId;
|
|
190
314
|
}
|
|
191
315
|
}
|
|
316
|
+
if (opts?.agentSessionKey &&
|
|
317
|
+
job &&
|
|
318
|
+
typeof job === "object" &&
|
|
319
|
+
"payload" in job &&
|
|
320
|
+
job.payload?.kind === "agentTurn") {
|
|
321
|
+
const deliveryValue = job.delivery;
|
|
322
|
+
const delivery = isRecord(deliveryValue) ? deliveryValue : undefined;
|
|
323
|
+
const modeRaw = typeof delivery?.mode === "string" ? delivery.mode : "";
|
|
324
|
+
const mode = modeRaw.trim().toLowerCase();
|
|
325
|
+
const hasTarget = (typeof delivery?.channel === "string" && delivery.channel.trim()) ||
|
|
326
|
+
(typeof delivery?.to === "string" && delivery.to.trim());
|
|
327
|
+
const shouldInfer = (deliveryValue == null || delivery) && mode !== "none" && !hasTarget;
|
|
328
|
+
if (shouldInfer) {
|
|
329
|
+
const inferred = inferDeliveryFromSessionKey(opts.agentSessionKey);
|
|
330
|
+
if (inferred) {
|
|
331
|
+
job.delivery = {
|
|
332
|
+
...delivery,
|
|
333
|
+
...inferred,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
192
338
|
const contextMessages = typeof params.contextMessages === "number" && Number.isFinite(params.contextMessages)
|
|
193
339
|
? params.contextMessages
|
|
194
340
|
: 0;
|
|
@@ -237,7 +383,8 @@ Use jobId as the canonical identifier; id is accepted for compatibility. Use con
|
|
|
237
383
|
if (!id) {
|
|
238
384
|
throw new Error("jobId required (id accepted for backward compatibility)");
|
|
239
385
|
}
|
|
240
|
-
|
|
386
|
+
const runMode = params.runMode === "due" || params.runMode === "force" ? params.runMode : "force";
|
|
387
|
+
return jsonResult(await callGatewayTool("cron.run", gatewayOpts, { id, mode: runMode }));
|
|
241
388
|
}
|
|
242
389
|
case "runs": {
|
|
243
390
|
const id = readStringParam(params, "jobId") ?? readStringParam(params, "id");
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { getGateway } from "../../discord/monitor/gateway-registry.js";
|
|
2
|
+
import { jsonResult, readStringParam } from "./common.js";
|
|
3
|
+
const ACTIVITY_TYPE_MAP = {
|
|
4
|
+
playing: 0,
|
|
5
|
+
streaming: 1,
|
|
6
|
+
listening: 2,
|
|
7
|
+
watching: 3,
|
|
8
|
+
custom: 4,
|
|
9
|
+
competing: 5,
|
|
10
|
+
};
|
|
11
|
+
const VALID_STATUSES = new Set(["online", "dnd", "idle", "invisible"]);
|
|
12
|
+
export async function handleDiscordPresenceAction(action, params, isActionEnabled) {
|
|
13
|
+
if (action !== "setPresence") {
|
|
14
|
+
throw new Error(`Unknown presence action: ${action}`);
|
|
15
|
+
}
|
|
16
|
+
if (!isActionEnabled("presence", false)) {
|
|
17
|
+
throw new Error("Discord presence changes are disabled.");
|
|
18
|
+
}
|
|
19
|
+
const accountId = readStringParam(params, "accountId");
|
|
20
|
+
const gateway = getGateway(accountId);
|
|
21
|
+
if (!gateway) {
|
|
22
|
+
throw new Error(`Discord gateway not available${accountId ? ` for account "${accountId}"` : ""}. The bot may not be connected.`);
|
|
23
|
+
}
|
|
24
|
+
if (!gateway.isConnected) {
|
|
25
|
+
throw new Error(`Discord gateway is not connected${accountId ? ` for account "${accountId}"` : ""}.`);
|
|
26
|
+
}
|
|
27
|
+
const statusRaw = readStringParam(params, "status") ?? "online";
|
|
28
|
+
if (!VALID_STATUSES.has(statusRaw)) {
|
|
29
|
+
throw new Error(`Invalid status "${statusRaw}". Must be one of: ${[...VALID_STATUSES].join(", ")}`);
|
|
30
|
+
}
|
|
31
|
+
const status = statusRaw;
|
|
32
|
+
const activityTypeRaw = readStringParam(params, "activityType");
|
|
33
|
+
const activityName = readStringParam(params, "activityName");
|
|
34
|
+
const activities = [];
|
|
35
|
+
if (activityTypeRaw || activityName) {
|
|
36
|
+
if (!activityTypeRaw) {
|
|
37
|
+
throw new Error("activityType is required when activityName is provided. " +
|
|
38
|
+
`Valid types: ${Object.keys(ACTIVITY_TYPE_MAP).join(", ")}`);
|
|
39
|
+
}
|
|
40
|
+
const typeNum = ACTIVITY_TYPE_MAP[activityTypeRaw.toLowerCase()];
|
|
41
|
+
if (typeNum === undefined) {
|
|
42
|
+
throw new Error(`Invalid activityType "${activityTypeRaw}". Must be one of: ${Object.keys(ACTIVITY_TYPE_MAP).join(", ")}`);
|
|
43
|
+
}
|
|
44
|
+
const activity = {
|
|
45
|
+
name: activityName ?? "",
|
|
46
|
+
type: typeNum,
|
|
47
|
+
};
|
|
48
|
+
// Streaming URL (Twitch/YouTube). May not render for bots but is the correct payload shape.
|
|
49
|
+
if (typeNum === 1) {
|
|
50
|
+
const url = readStringParam(params, "activityUrl");
|
|
51
|
+
if (url) {
|
|
52
|
+
activity.url = url;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const state = readStringParam(params, "activityState");
|
|
56
|
+
if (state) {
|
|
57
|
+
activity.state = state;
|
|
58
|
+
}
|
|
59
|
+
activities.push(activity);
|
|
60
|
+
}
|
|
61
|
+
const presenceData = {
|
|
62
|
+
since: null,
|
|
63
|
+
activities,
|
|
64
|
+
status,
|
|
65
|
+
afk: false,
|
|
66
|
+
};
|
|
67
|
+
gateway.updatePresence(presenceData);
|
|
68
|
+
return jsonResult({
|
|
69
|
+
ok: true,
|
|
70
|
+
status,
|
|
71
|
+
activities: activities.map((a) => ({
|
|
72
|
+
type: a.type,
|
|
73
|
+
name: a.name,
|
|
74
|
+
...(a.url ? { url: a.url } : {}),
|
|
75
|
+
...(a.state ? { state: a.state } : {}),
|
|
76
|
+
})),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { complete, } from "@mariozechner/pi-ai";
|
|
4
|
-
import { discoverAuthStorage, discoverModels } from "
|
|
4
|
+
import { discoverAuthStorage, discoverModels } from "../pi-model-discovery.js";
|
|
5
5
|
import { Type } from "@sinclair/typebox";
|
|
6
6
|
import { resolveUserPath } from "../../utils.js";
|
|
7
7
|
import { loadWebMedia } from "../../web/media.js";
|
|
@@ -11,8 +11,20 @@ import { normalizeAccountId } from "../../routing/session-key.js";
|
|
|
11
11
|
import { channelTargetSchema, channelTargetsSchema, stringEnum } from "../schema/typebox.js";
|
|
12
12
|
import { listChannelSupportedActions } from "../channel-tools.js";
|
|
13
13
|
import { normalizeMessageChannel } from "../../utils/message-channel.js";
|
|
14
|
+
import { stripReasoningTagsFromText } from "../../shared/text/reasoning-tags.js";
|
|
14
15
|
import { jsonResult, readNumberParam, readStringParam } from "./common.js";
|
|
15
16
|
const AllMessageActions = CHANNEL_MESSAGE_ACTION_NAMES;
|
|
17
|
+
const EXPLICIT_TARGET_ACTIONS = new Set([
|
|
18
|
+
"send",
|
|
19
|
+
"sendWithEffect",
|
|
20
|
+
"sendAttachment",
|
|
21
|
+
"reply",
|
|
22
|
+
"thread-reply",
|
|
23
|
+
"broadcast",
|
|
24
|
+
]);
|
|
25
|
+
function actionNeedsExplicitTarget(action) {
|
|
26
|
+
return EXPLICIT_TARGET_ACTIONS.has(action);
|
|
27
|
+
}
|
|
16
28
|
function buildRoutingSchema() {
|
|
17
29
|
return {
|
|
18
30
|
channel: Type.Optional(Type.String()),
|
|
@@ -29,7 +41,9 @@ function buildSendSchema(options) {
|
|
|
29
41
|
description: "Message effect name/id for sendWithEffect (e.g., invisible ink).",
|
|
30
42
|
})),
|
|
31
43
|
effect: Type.Optional(Type.String({ description: "Alias for effectId (e.g., invisible-ink, balloons)." })),
|
|
32
|
-
media: Type.Optional(Type.String(
|
|
44
|
+
media: Type.Optional(Type.String({
|
|
45
|
+
description: "Media URL or local path. data: URLs are not supported here, use buffer.",
|
|
46
|
+
})),
|
|
33
47
|
filename: Type.Optional(Type.String()),
|
|
34
48
|
buffer: Type.Optional(Type.String({
|
|
35
49
|
description: "Base64 payload for attachments (optionally a data: URL).",
|
|
@@ -45,6 +59,7 @@ function buildSendSchema(options) {
|
|
|
45
59
|
silent: Type.Optional(Type.Boolean()),
|
|
46
60
|
bestEffort: Type.Optional(Type.Boolean()),
|
|
47
61
|
gifPlayback: Type.Optional(Type.Boolean()),
|
|
62
|
+
quoteText: Type.Optional(Type.String({ description: "Quote text for Telegram reply_parameters" })),
|
|
48
63
|
buttons: Type.Optional(Type.Array(Type.Array(Type.Object({
|
|
49
64
|
text: Type.String(),
|
|
50
65
|
callback_data: Type.String(),
|
|
@@ -159,6 +174,23 @@ function buildChannelManagementSchema() {
|
|
|
159
174
|
})),
|
|
160
175
|
};
|
|
161
176
|
}
|
|
177
|
+
function buildPresenceSchema() {
|
|
178
|
+
return {
|
|
179
|
+
activityType: Type.Optional(Type.String({
|
|
180
|
+
description: "Activity type: playing, streaming, listening, watching, competing, custom.",
|
|
181
|
+
})),
|
|
182
|
+
activityName: Type.Optional(Type.String({
|
|
183
|
+
description: "Activity name shown in sidebar (e.g. 'with fire'). Ignored for custom type.",
|
|
184
|
+
})),
|
|
185
|
+
activityUrl: Type.Optional(Type.String({
|
|
186
|
+
description: "Streaming URL (Twitch or YouTube). Only used with streaming type; may not render for bots.",
|
|
187
|
+
})),
|
|
188
|
+
activityState: Type.Optional(Type.String({
|
|
189
|
+
description: "State text. For custom type this is the status text; for others it shows in the flyout.",
|
|
190
|
+
})),
|
|
191
|
+
status: Type.Optional(Type.String({ description: "Bot status: online, dnd, idle, invisible." })),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
162
194
|
function buildMessageToolSchemaProps(options) {
|
|
163
195
|
return {
|
|
164
196
|
...buildRoutingSchema(),
|
|
@@ -173,6 +205,7 @@ function buildMessageToolSchemaProps(options) {
|
|
|
173
205
|
...buildModerationSchema(),
|
|
174
206
|
...buildGatewaySchema(),
|
|
175
207
|
...buildChannelManagementSchema(),
|
|
208
|
+
...buildPresenceSchema(),
|
|
176
209
|
};
|
|
177
210
|
}
|
|
178
211
|
function buildMessageToolSchemaFromActions(actions, options) {
|
|
@@ -266,11 +299,31 @@ export function createMessageTool(options) {
|
|
|
266
299
|
err.name = "AbortError";
|
|
267
300
|
throw err;
|
|
268
301
|
}
|
|
269
|
-
|
|
302
|
+
// Shallow-copy so we don't mutate the original event args (used for logging/dedup).
|
|
303
|
+
const params = { ...args };
|
|
304
|
+
// Strip reasoning tags from text fields — models may include <think>…</think>
|
|
305
|
+
// in tool arguments, and the messaging tool send path has no other tag filtering.
|
|
306
|
+
for (const field of ["text", "content", "message", "caption"]) {
|
|
307
|
+
if (typeof params[field] === "string") {
|
|
308
|
+
params[field] = stripReasoningTagsFromText(params[field]);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
270
311
|
const cfg = options?.config ?? loadConfig();
|
|
271
312
|
const action = readStringParam(params, "action", {
|
|
272
313
|
required: true,
|
|
273
314
|
});
|
|
315
|
+
// Enforce explicit target when required (e.g. cron/hook-initiated runs).
|
|
316
|
+
const requireExplicitTarget = options?.requireExplicitTarget === true;
|
|
317
|
+
if (requireExplicitTarget && actionNeedsExplicitTarget(action)) {
|
|
318
|
+
const explicitTarget = (typeof params.target === "string" && params.target.trim().length > 0) ||
|
|
319
|
+
(typeof params.to === "string" && params.to.trim().length > 0) ||
|
|
320
|
+
(typeof params.channelId === "string" && params.channelId.trim().length > 0) ||
|
|
321
|
+
(Array.isArray(params.targets) &&
|
|
322
|
+
params.targets.some((value) => typeof value === "string" && value.trim().length > 0));
|
|
323
|
+
if (!explicitTarget) {
|
|
324
|
+
throw new Error("Explicit message target required for this run. Provide target/targets (and channel when needed).");
|
|
325
|
+
}
|
|
326
|
+
}
|
|
274
327
|
const accountId = readStringParam(params, "accountId") ?? agentAccountId;
|
|
275
328
|
if (accountId) {
|
|
276
329
|
params.accountId = accountId;
|
|
@@ -309,6 +362,7 @@ export function createMessageTool(options) {
|
|
|
309
362
|
agentId: options?.agentSessionKey
|
|
310
363
|
? resolveSessionAgentId({ sessionKey: options.agentSessionKey, config: cfg })
|
|
311
364
|
: undefined,
|
|
365
|
+
sandboxRoot: options?.sandboxRoot,
|
|
312
366
|
abortSignal: signal,
|
|
313
367
|
});
|
|
314
368
|
const toolResult = getToolResult(result);
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
2
|
import { loadConfig } from "../../config/config.js";
|
|
3
3
|
import { callGateway } from "../../gateway/call.js";
|
|
4
|
+
import { capArrayByJsonBytes } from "../../gateway/session-utils.fs.js";
|
|
4
5
|
import { isSubagentSessionKey, resolveAgentIdFromSessionKey } from "../../routing/session-key.js";
|
|
6
|
+
import { truncateUtf16Safe } from "../../utils.js";
|
|
5
7
|
import { jsonResult, readStringParam } from "./common.js";
|
|
6
8
|
import { createAgentToAgentPolicy, resolveSessionReference, resolveMainSessionAlias, resolveInternalSessionKey, stripToolMessages, } from "./sessions-helpers.js";
|
|
7
9
|
const SessionsHistoryToolSchema = Type.Object({
|
|
@@ -9,6 +11,63 @@ const SessionsHistoryToolSchema = Type.Object({
|
|
|
9
11
|
limit: Type.Optional(Type.Number({ minimum: 1 })),
|
|
10
12
|
includeTools: Type.Optional(Type.Boolean()),
|
|
11
13
|
});
|
|
14
|
+
/** Hard cap on the serialized JSON size of the returned messages array. */
|
|
15
|
+
const SESSIONS_HISTORY_MAX_BYTES = 512 * 1024;
|
|
16
|
+
/** Per-block text truncation limit (in UTF-16 chars). */
|
|
17
|
+
const SESSIONS_HISTORY_TEXT_MAX_CHARS = 32_000;
|
|
18
|
+
function truncateHistoryText(text, maxChars) {
|
|
19
|
+
if (text.length <= maxChars)
|
|
20
|
+
return text;
|
|
21
|
+
return `${truncateUtf16Safe(text, maxChars)}…(truncated)`;
|
|
22
|
+
}
|
|
23
|
+
function sanitizeHistoryContentBlock(block) {
|
|
24
|
+
if (!block || typeof block !== "object")
|
|
25
|
+
return block;
|
|
26
|
+
const rec = block;
|
|
27
|
+
if (typeof rec.text === "string") {
|
|
28
|
+
return { ...rec, text: truncateHistoryText(rec.text, SESSIONS_HISTORY_TEXT_MAX_CHARS) };
|
|
29
|
+
}
|
|
30
|
+
return block;
|
|
31
|
+
}
|
|
32
|
+
function sanitizeHistoryMessage(msg) {
|
|
33
|
+
if (!msg || typeof msg !== "object")
|
|
34
|
+
return msg;
|
|
35
|
+
const rec = msg;
|
|
36
|
+
if (typeof rec.content === "string") {
|
|
37
|
+
return { ...rec, content: truncateHistoryText(rec.content, SESSIONS_HISTORY_TEXT_MAX_CHARS) };
|
|
38
|
+
}
|
|
39
|
+
if (Array.isArray(rec.content)) {
|
|
40
|
+
return { ...rec, content: rec.content.map(sanitizeHistoryContentBlock) };
|
|
41
|
+
}
|
|
42
|
+
return msg;
|
|
43
|
+
}
|
|
44
|
+
function jsonUtf8Bytes(value) {
|
|
45
|
+
try {
|
|
46
|
+
return Buffer.byteLength(JSON.stringify(value), "utf8");
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return Buffer.byteLength(String(value), "utf8");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function enforceSessionsHistoryHardCap(messages) {
|
|
53
|
+
const sanitized = messages.map(sanitizeHistoryMessage);
|
|
54
|
+
const { items, bytes } = capArrayByJsonBytes(sanitized, SESSIONS_HISTORY_MAX_BYTES);
|
|
55
|
+
const droppedMessages = sanitized.length - items.length;
|
|
56
|
+
// Count how many messages had content truncated by comparing original vs sanitized text.
|
|
57
|
+
let contentTruncated = 0;
|
|
58
|
+
for (let i = 0; i < messages.length; i++) {
|
|
59
|
+
if (jsonUtf8Bytes(messages[i]) !== jsonUtf8Bytes(sanitized[i])) {
|
|
60
|
+
contentTruncated++;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
messages: items,
|
|
65
|
+
truncated: droppedMessages > 0 || contentTruncated > 0,
|
|
66
|
+
droppedMessages,
|
|
67
|
+
contentTruncated,
|
|
68
|
+
bytes,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
12
71
|
function resolveSandboxSessionToolsVisibility(cfg) {
|
|
13
72
|
return cfg.agents?.defaults?.sandbox?.sessionToolsVisibility ?? "spawned";
|
|
14
73
|
}
|
|
@@ -109,9 +168,18 @@ export function createSessionsHistoryTool(opts) {
|
|
|
109
168
|
}));
|
|
110
169
|
const rawMessages = Array.isArray(result?.messages) ? result.messages : [];
|
|
111
170
|
const messages = includeTools ? rawMessages : stripToolMessages(rawMessages);
|
|
171
|
+
const capped = enforceSessionsHistoryHardCap(messages);
|
|
112
172
|
return jsonResult({
|
|
113
173
|
sessionKey: displayKey,
|
|
114
|
-
messages,
|
|
174
|
+
messages: capped.messages,
|
|
175
|
+
...(capped.truncated
|
|
176
|
+
? {
|
|
177
|
+
truncated: true,
|
|
178
|
+
droppedMessages: capped.droppedMessages,
|
|
179
|
+
contentTruncated: capped.contentTruncated,
|
|
180
|
+
bytes: capped.bytes,
|
|
181
|
+
}
|
|
182
|
+
: {}),
|
|
115
183
|
});
|
|
116
184
|
},
|
|
117
185
|
};
|