@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,14 +1,14 @@
|
|
|
1
1
|
import { WebSocketServer } from "ws";
|
|
2
2
|
import { CANVAS_HOST_PATH } from "../canvas-host/a2ui.js";
|
|
3
3
|
import { createCanvasHostHandler } from "../canvas-host/server.js";
|
|
4
|
-
import { createGatewayHooksRequestHandler } from "./server/hooks.js";
|
|
5
|
-
import { listenGatewayHttpServer } from "./server/http-listen.js";
|
|
6
4
|
import { resolveGatewayListenHosts } from "./net.js";
|
|
7
|
-
import { createGatewayPluginRequestHandler } from "./server/plugins-http.js";
|
|
8
5
|
import { createGatewayBroadcaster } from "./server-broadcast.js";
|
|
9
|
-
import { createChatRunState } from "./server-chat.js";
|
|
6
|
+
import { createChatRunState, createToolEventRecipientRegistry, } from "./server-chat.js";
|
|
10
7
|
import { MAX_PAYLOAD_BYTES } from "./server-constants.js";
|
|
11
8
|
import { attachGatewayUpgradeHandler, createGatewayHttpServer } from "./server-http.js";
|
|
9
|
+
import { createGatewayHooksRequestHandler } from "./server/hooks.js";
|
|
10
|
+
import { listenGatewayHttpServer } from "./server/http-listen.js";
|
|
11
|
+
import { createGatewayPluginRequestHandler } from "./server/plugins-http.js";
|
|
12
12
|
export async function createGatewayRuntimeState(params) {
|
|
13
13
|
let canvasHost = null;
|
|
14
14
|
if (params.canvasHostEnabled) {
|
|
@@ -29,6 +29,10 @@ export async function createGatewayRuntimeState(params) {
|
|
|
29
29
|
params.logCanvas.warn(`canvas host failed to start: ${String(err)}`);
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
+
// Create clients set before HTTP server so it can be passed to both
|
|
33
|
+
// createGatewayHttpServer (for canvas auth) and attachGatewayUpgradeHandler
|
|
34
|
+
const clients = new Set();
|
|
35
|
+
const { broadcast, broadcastToConnIds } = createGatewayBroadcaster({ clients });
|
|
32
36
|
const handleHooksRequest = createGatewayHooksRequestHandler({
|
|
33
37
|
deps: params.deps,
|
|
34
38
|
getHooksConfig: params.hooksConfig,
|
|
@@ -46,8 +50,10 @@ export async function createGatewayRuntimeState(params) {
|
|
|
46
50
|
for (const host of bindHosts) {
|
|
47
51
|
const httpServer = createGatewayHttpServer({
|
|
48
52
|
canvasHost,
|
|
53
|
+
clients,
|
|
49
54
|
controlUiEnabled: params.controlUiEnabled,
|
|
50
55
|
controlUiBasePath: params.controlUiBasePath,
|
|
56
|
+
controlUiRoot: params.controlUiRoot,
|
|
51
57
|
openAiChatCompletionsEnabled: params.openAiChatCompletionsEnabled,
|
|
52
58
|
openResponsesEnabled: params.openResponsesEnabled,
|
|
53
59
|
openResponsesConfig: params.openResponsesConfig,
|
|
@@ -66,8 +72,9 @@ export async function createGatewayRuntimeState(params) {
|
|
|
66
72
|
httpBindHosts.push(host);
|
|
67
73
|
}
|
|
68
74
|
catch (err) {
|
|
69
|
-
if (host === bindHosts[0])
|
|
75
|
+
if (host === bindHosts[0]) {
|
|
70
76
|
throw err;
|
|
77
|
+
}
|
|
71
78
|
params.log.warn(`gateway: failed to bind loopback alias ${host}:${params.port} (${String(err)})`);
|
|
72
79
|
}
|
|
73
80
|
}
|
|
@@ -80,10 +87,14 @@ export async function createGatewayRuntimeState(params) {
|
|
|
80
87
|
maxPayload: MAX_PAYLOAD_BYTES,
|
|
81
88
|
});
|
|
82
89
|
for (const server of httpServers) {
|
|
83
|
-
attachGatewayUpgradeHandler({
|
|
90
|
+
attachGatewayUpgradeHandler({
|
|
91
|
+
httpServer: server,
|
|
92
|
+
wss,
|
|
93
|
+
canvasHost,
|
|
94
|
+
clients,
|
|
95
|
+
resolvedAuth: params.resolvedAuth,
|
|
96
|
+
});
|
|
84
97
|
}
|
|
85
|
-
const clients = new Set();
|
|
86
|
-
const { broadcast } = createGatewayBroadcaster({ clients });
|
|
87
98
|
const agentRunSeq = new Map();
|
|
88
99
|
const dedupe = new Map();
|
|
89
100
|
const chatRunState = createChatRunState();
|
|
@@ -93,6 +104,7 @@ export async function createGatewayRuntimeState(params) {
|
|
|
93
104
|
const addChatRun = chatRunRegistry.add;
|
|
94
105
|
const removeChatRun = chatRunRegistry.remove;
|
|
95
106
|
const chatAbortControllers = new Map();
|
|
107
|
+
const toolEventRecipients = createToolEventRecipientRegistry();
|
|
96
108
|
return {
|
|
97
109
|
canvasHost,
|
|
98
110
|
httpServer,
|
|
@@ -101,6 +113,7 @@ export async function createGatewayRuntimeState(params) {
|
|
|
101
113
|
wss,
|
|
102
114
|
clients,
|
|
103
115
|
broadcast,
|
|
116
|
+
broadcastToConnIds,
|
|
104
117
|
agentRunSeq,
|
|
105
118
|
dedupe,
|
|
106
119
|
chatRunState,
|
|
@@ -109,5 +122,6 @@ export async function createGatewayRuntimeState(params) {
|
|
|
109
122
|
addChatRun,
|
|
110
123
|
removeChatRun,
|
|
111
124
|
chatAbortControllers,
|
|
125
|
+
toolEventRecipients,
|
|
112
126
|
};
|
|
113
127
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { resolveDefaultAgentId } from "../agents/agent-scope.js";
|
|
2
|
+
import { resolveMemoryBackendConfig } from "../memory/backend-config.js";
|
|
3
|
+
import { getMemorySearchManager } from "../memory/index.js";
|
|
4
|
+
export async function startGatewayMemoryBackend(params) {
|
|
5
|
+
const agentId = resolveDefaultAgentId(params.cfg);
|
|
6
|
+
const resolved = resolveMemoryBackendConfig({ cfg: params.cfg, agentId });
|
|
7
|
+
if (resolved.backend !== "qmd" || !resolved.qmd) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const { manager, error } = await getMemorySearchManager({ cfg: params.cfg, agentId });
|
|
11
|
+
if (!manager) {
|
|
12
|
+
params.log.warn(`qmd memory startup initialization failed for agent "${agentId}": ${error ?? "unknown error"}`);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
params.log.info?.(`qmd memory startup initialization armed for agent "${agentId}"`);
|
|
16
|
+
}
|
|
@@ -156,7 +156,7 @@ export async function startGatewayServer(port = 18789, opts = {}) {
|
|
|
156
156
|
if (cfgAtStart.gateway?.tls?.enabled && !gatewayTls.enabled) {
|
|
157
157
|
throw new Error(gatewayTls.error ?? "gateway tls: failed to enable");
|
|
158
158
|
}
|
|
159
|
-
const { canvasHost, httpServer, httpServers, httpBindHosts, wss, clients, broadcast, agentRunSeq, dedupe, chatRunState, chatRunBuffers, chatDeltaSentAt, addChatRun, removeChatRun, chatAbortControllers, } = await createGatewayRuntimeState({
|
|
159
|
+
const { canvasHost, httpServer, httpServers, httpBindHosts, wss, clients, broadcast, agentRunSeq, dedupe, chatRunState, chatRunBuffers, chatDeltaSentAt, addChatRun, removeChatRun, chatAbortControllers, broadcastToConnIds, toolEventRecipients, } = await createGatewayRuntimeState({
|
|
160
160
|
cfg: cfgAtStart,
|
|
161
161
|
bindHost,
|
|
162
162
|
port,
|
|
@@ -257,11 +257,13 @@ export async function startGatewayServer(port = 18789, opts = {}) {
|
|
|
257
257
|
});
|
|
258
258
|
const agentUnsub = onAgentEvent(createAgentEventHandler({
|
|
259
259
|
broadcast,
|
|
260
|
+
broadcastToConnIds,
|
|
260
261
|
nodeSendToSession,
|
|
261
262
|
agentRunSeq,
|
|
262
263
|
chatRunState,
|
|
263
264
|
resolveSessionKeyForRun,
|
|
264
265
|
clearAgentRunContext,
|
|
266
|
+
toolEventRecipients,
|
|
265
267
|
}));
|
|
266
268
|
const heartbeatUnsub = onHeartbeatEvent((evt) => {
|
|
267
269
|
broadcast("heartbeat", evt, { dropIfSlow: true });
|
|
@@ -304,6 +306,7 @@ export async function startGatewayServer(port = 18789, opts = {}) {
|
|
|
304
306
|
incrementPresenceVersion,
|
|
305
307
|
getHealthVersion,
|
|
306
308
|
broadcast,
|
|
309
|
+
broadcastToConnIds,
|
|
307
310
|
nodeSendToSession,
|
|
308
311
|
nodeSendToAllSubscribed,
|
|
309
312
|
nodeSubscribe,
|
|
@@ -318,6 +321,7 @@ export async function startGatewayServer(port = 18789, opts = {}) {
|
|
|
318
321
|
chatDeltaSentAt: chatRunState.deltaSentAt,
|
|
319
322
|
addChatRun,
|
|
320
323
|
removeChatRun,
|
|
324
|
+
registerToolEventRecipient: toolEventRecipients.add,
|
|
321
325
|
dedupe,
|
|
322
326
|
wizardSessions,
|
|
323
327
|
findRunningWizard,
|
|
@@ -2,6 +2,8 @@ import fs from "node:fs";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { resolveSessionTranscriptPath } from "../config/sessions.js";
|
|
5
|
+
import { resolveRequiredHomeDir } from "../infra/home-dir.js";
|
|
6
|
+
import { extractToolCallNames, hasToolCall } from "../utils/transcript-tools.js";
|
|
5
7
|
import { stripEnvelope } from "./chat-sanitize.js";
|
|
6
8
|
export function readSessionMessages(sessionId, storePath, sessionFile) {
|
|
7
9
|
const candidates = resolveSessionTranscriptCandidates(sessionId, storePath, sessionFile);
|
|
@@ -17,6 +19,22 @@ export function readSessionMessages(sessionId, storePath, sessionFile) {
|
|
|
17
19
|
const parsed = JSON.parse(line);
|
|
18
20
|
if (parsed?.message) {
|
|
19
21
|
messages.push(parsed.message);
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
// Compaction entries are not "message" records, but they're useful context for debugging.
|
|
25
|
+
// Emit a lightweight synthetic message that the Web UI can render as a divider.
|
|
26
|
+
if (parsed?.type === "compaction") {
|
|
27
|
+
const ts = typeof parsed.timestamp === "string" ? Date.parse(parsed.timestamp) : Number.NaN;
|
|
28
|
+
const timestamp = Number.isFinite(ts) ? ts : Date.now();
|
|
29
|
+
messages.push({
|
|
30
|
+
role: "system",
|
|
31
|
+
content: [{ type: "text", text: "Compaction" }],
|
|
32
|
+
timestamp,
|
|
33
|
+
__poolbot: {
|
|
34
|
+
kind: "compaction",
|
|
35
|
+
id: typeof parsed.id === "string" ? parsed.id : undefined,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
20
38
|
}
|
|
21
39
|
}
|
|
22
40
|
catch {
|
|
@@ -36,7 +54,8 @@ export function resolveSessionTranscriptCandidates(sessionId, storePath, session
|
|
|
36
54
|
if (agentId) {
|
|
37
55
|
candidates.push(resolveSessionTranscriptPath(sessionId, agentId));
|
|
38
56
|
}
|
|
39
|
-
|
|
57
|
+
const home = resolveRequiredHomeDir(process.env, os.homedir);
|
|
58
|
+
candidates.push(path.join(home, ".poolbot", "sessions", `${sessionId}.jsonl`));
|
|
40
59
|
return candidates;
|
|
41
60
|
}
|
|
42
61
|
export function archiveFileOnDisk(filePath, reason) {
|
|
@@ -214,31 +233,10 @@ function extractPreviewText(message) {
|
|
|
214
233
|
return null;
|
|
215
234
|
}
|
|
216
235
|
function isToolCall(message) {
|
|
217
|
-
|
|
218
|
-
return true;
|
|
219
|
-
if (!Array.isArray(message.content))
|
|
220
|
-
return false;
|
|
221
|
-
return message.content.some((entry) => {
|
|
222
|
-
if (entry?.name)
|
|
223
|
-
return true;
|
|
224
|
-
const raw = typeof entry?.type === "string" ? entry.type.toLowerCase() : "";
|
|
225
|
-
return raw === "toolcall" || raw === "tool_call";
|
|
226
|
-
});
|
|
236
|
+
return hasToolCall(message);
|
|
227
237
|
}
|
|
228
238
|
function extractToolNames(message) {
|
|
229
|
-
|
|
230
|
-
if (Array.isArray(message.content)) {
|
|
231
|
-
for (const entry of message.content) {
|
|
232
|
-
if (typeof entry?.name === "string" && entry.name.trim()) {
|
|
233
|
-
names.push(entry.name.trim());
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
const toolName = typeof message.toolName === "string" ? message.toolName : message.tool_name;
|
|
238
|
-
if (typeof toolName === "string" && toolName.trim()) {
|
|
239
|
-
names.push(toolName.trim());
|
|
240
|
-
}
|
|
241
|
-
return names;
|
|
239
|
+
return extractToolCallNames(message);
|
|
242
240
|
}
|
|
243
241
|
function extractMediaSummary(message) {
|
|
244
242
|
if (!Array.isArray(message.content))
|
|
@@ -316,7 +314,7 @@ function readRecentMessagesFromTranscript(filePath, maxMessages, readBytes) {
|
|
|
316
314
|
// skip malformed lines
|
|
317
315
|
}
|
|
318
316
|
}
|
|
319
|
-
return collected.
|
|
317
|
+
return collected.toReversed();
|
|
320
318
|
}
|
|
321
319
|
catch {
|
|
322
320
|
return [];
|
|
@@ -3,11 +3,12 @@ import path from "node:path";
|
|
|
3
3
|
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js";
|
|
4
4
|
import { lookupContextTokens } from "../agents/context.js";
|
|
5
5
|
import { DEFAULT_CONTEXT_TOKENS, DEFAULT_MODEL, DEFAULT_PROVIDER } from "../agents/defaults.js";
|
|
6
|
-
import { resolveConfiguredModelRef } from "../agents/model-selection.js";
|
|
6
|
+
import { resolveConfiguredModelRef, resolveDefaultModelForAgent, } from "../agents/model-selection.js";
|
|
7
7
|
import { loadConfig } from "../config/config.js";
|
|
8
8
|
import { resolveStateDir } from "../config/paths.js";
|
|
9
9
|
import { buildGroupDisplayName, canonicalizeMainSessionAlias, loadSessionStore, resolveMainSessionKey, resolveStorePath, } from "../config/sessions.js";
|
|
10
10
|
import { normalizeAgentId, normalizeMainKey, parseAgentSessionKey, } from "../routing/session-key.js";
|
|
11
|
+
import { isCronRunSessionKey } from "../sessions/session-key-utils.js";
|
|
11
12
|
import { normalizeSessionDeliveryFields } from "../utils/delivery-context.js";
|
|
12
13
|
import { readFirstUserMessageFromTranscript, readLastMessagePreviewFromTranscript, } from "./session-utils.fs.js";
|
|
13
14
|
export { archiveFileOnDisk, capArrayByJsonBytes, readFirstUserMessageFromTranscript, readLastMessagePreviewFromTranscript, readSessionPreviewItemsFromTranscript, readSessionMessages, resolveSessionTranscriptCandidates, } from "./session-utils.fs.js";
|
|
@@ -374,12 +375,14 @@ export function getSessionDefaults(cfg) {
|
|
|
374
375
|
contextTokens: contextTokens ?? null,
|
|
375
376
|
};
|
|
376
377
|
}
|
|
377
|
-
export function resolveSessionModelRef(cfg, entry) {
|
|
378
|
-
const resolved =
|
|
379
|
-
cfg,
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
378
|
+
export function resolveSessionModelRef(cfg, entry, agentId) {
|
|
379
|
+
const resolved = agentId
|
|
380
|
+
? resolveDefaultModelForAgent({ cfg, agentId })
|
|
381
|
+
: resolveConfiguredModelRef({
|
|
382
|
+
cfg,
|
|
383
|
+
defaultProvider: DEFAULT_PROVIDER,
|
|
384
|
+
defaultModel: DEFAULT_MODEL,
|
|
385
|
+
});
|
|
383
386
|
let provider = resolved.provider;
|
|
384
387
|
let model = resolved.model;
|
|
385
388
|
const storedModelOverride = entry?.modelOverride?.trim();
|
|
@@ -405,6 +408,8 @@ export function listSessionsFromStore(params) {
|
|
|
405
408
|
: undefined;
|
|
406
409
|
let sessions = Object.entries(store)
|
|
407
410
|
.filter(([key]) => {
|
|
411
|
+
if (isCronRunSessionKey(key))
|
|
412
|
+
return false;
|
|
408
413
|
if (!includeGlobal && key === "global")
|
|
409
414
|
return false;
|
|
410
415
|
if (!includeUnknown && key === "unknown")
|
|
@@ -458,6 +463,11 @@ export function listSessionsFromStore(params) {
|
|
|
458
463
|
entry?.label ??
|
|
459
464
|
originLabel;
|
|
460
465
|
const deliveryFields = normalizeSessionDeliveryFields(entry);
|
|
466
|
+
const parsedAgent = parseAgentSessionKey(key);
|
|
467
|
+
const sessionAgentId = normalizeAgentId(parsedAgent?.agentId ?? resolveDefaultAgentId(cfg));
|
|
468
|
+
const resolvedModel = resolveSessionModelRef(cfg, entry, sessionAgentId);
|
|
469
|
+
const modelProvider = resolvedModel.provider ?? DEFAULT_PROVIDER;
|
|
470
|
+
const model = resolvedModel.model ?? DEFAULT_MODEL;
|
|
461
471
|
return {
|
|
462
472
|
key,
|
|
463
473
|
entry,
|
|
@@ -483,8 +493,8 @@ export function listSessionsFromStore(params) {
|
|
|
483
493
|
outputTokens: entry?.outputTokens,
|
|
484
494
|
totalTokens: total,
|
|
485
495
|
responseUsage: entry?.responseUsage,
|
|
486
|
-
modelProvider
|
|
487
|
-
model
|
|
496
|
+
modelProvider,
|
|
497
|
+
model,
|
|
488
498
|
contextTokens: entry?.contextTokens,
|
|
489
499
|
deliveryContext: deliveryFields.deliveryContext,
|
|
490
500
|
lastChannel: deliveryFields.lastChannel ?? entry?.lastChannel,
|
|
@@ -492,7 +502,7 @@ export function listSessionsFromStore(params) {
|
|
|
492
502
|
lastAccountId: deliveryFields.lastAccountId ?? entry?.lastAccountId,
|
|
493
503
|
};
|
|
494
504
|
})
|
|
495
|
-
.
|
|
505
|
+
.toSorted((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0));
|
|
496
506
|
if (search) {
|
|
497
507
|
sessions = sessions.filter((s) => {
|
|
498
508
|
const fields = [s.displayName, s.label, s.subject, s.sessionId, s.key];
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { resolveAllowedModelRef, resolveDefaultModelForAgent } from "../agents/model-selection.js";
|
|
3
|
+
import { resolveDefaultAgentId } from "../agents/agent-scope.js";
|
|
4
4
|
import { normalizeGroupActivation } from "../auto-reply/group-activation.js";
|
|
5
5
|
import { formatThinkingLevels, formatXHighModelHint, normalizeElevatedLevel, normalizeReasoningLevel, normalizeThinkLevel, normalizeUsageDisplay, supportsXHighThinking, } from "../auto-reply/thinking.js";
|
|
6
|
-
import { isSubagentSessionKey } from "../routing/session-key.js";
|
|
6
|
+
import { isSubagentSessionKey, normalizeAgentId, parseAgentSessionKey, } from "../routing/session-key.js";
|
|
7
7
|
import { applyVerboseOverride, parseVerboseOverride } from "../sessions/level-overrides.js";
|
|
8
8
|
import { normalizeSendPolicy } from "../sessions/send-policy.js";
|
|
9
9
|
import { parseSessionLabel } from "../sessions/session-label.js";
|
|
@@ -36,6 +36,9 @@ function normalizeExecAsk(raw) {
|
|
|
36
36
|
export async function applySessionsPatchToStore(params) {
|
|
37
37
|
const { cfg, store, storeKey, patch } = params;
|
|
38
38
|
const now = Date.now();
|
|
39
|
+
const parsedAgent = parseAgentSessionKey(storeKey);
|
|
40
|
+
const sessionAgentId = normalizeAgentId(parsedAgent?.agentId ?? resolveDefaultAgentId(cfg));
|
|
41
|
+
const resolvedDefault = resolveDefaultModelForAgent({ cfg, agentId: sessionAgentId });
|
|
39
42
|
const existing = store[storeKey];
|
|
40
43
|
const next = existing
|
|
41
44
|
? {
|
|
@@ -89,19 +92,11 @@ export async function applySessionsPatchToStore(params) {
|
|
|
89
92
|
else if (raw !== undefined) {
|
|
90
93
|
const normalized = normalizeThinkLevel(String(raw));
|
|
91
94
|
if (!normalized) {
|
|
92
|
-
const resolvedDefault = resolveConfiguredModelRef({
|
|
93
|
-
cfg,
|
|
94
|
-
defaultProvider: DEFAULT_PROVIDER,
|
|
95
|
-
defaultModel: DEFAULT_MODEL,
|
|
96
|
-
});
|
|
97
95
|
const hintProvider = existing?.providerOverride?.trim() || resolvedDefault.provider;
|
|
98
96
|
const hintModel = existing?.modelOverride?.trim() || resolvedDefault.model;
|
|
99
97
|
return invalid(`invalid thinkingLevel (use ${formatThinkingLevels(hintProvider, hintModel, "|")})`);
|
|
100
98
|
}
|
|
101
|
-
|
|
102
|
-
delete next.thinkingLevel;
|
|
103
|
-
else
|
|
104
|
-
next.thinkingLevel = normalized;
|
|
99
|
+
next.thinkingLevel = normalized;
|
|
105
100
|
}
|
|
106
101
|
}
|
|
107
102
|
if ("verboseLevel" in patch) {
|
|
@@ -205,11 +200,6 @@ export async function applySessionsPatchToStore(params) {
|
|
|
205
200
|
}
|
|
206
201
|
if ("model" in patch) {
|
|
207
202
|
const raw = patch.model;
|
|
208
|
-
const resolvedDefault = resolveConfiguredModelRef({
|
|
209
|
-
cfg,
|
|
210
|
-
defaultProvider: DEFAULT_PROVIDER,
|
|
211
|
-
defaultModel: DEFAULT_MODEL,
|
|
212
|
-
});
|
|
213
203
|
if (raw === null) {
|
|
214
204
|
applyModelOverrideToSessionEntry({
|
|
215
205
|
entry: next,
|
|
@@ -254,11 +244,6 @@ export async function applySessionsPatchToStore(params) {
|
|
|
254
244
|
}
|
|
255
245
|
}
|
|
256
246
|
if (next.thinkingLevel === "xhigh") {
|
|
257
|
-
const resolvedDefault = resolveConfiguredModelRef({
|
|
258
|
-
cfg,
|
|
259
|
-
defaultProvider: DEFAULT_PROVIDER,
|
|
260
|
-
defaultModel: DEFAULT_MODEL,
|
|
261
|
-
});
|
|
262
247
|
const effectiveProvider = next.providerOverride ?? resolvedDefault.provider;
|
|
263
248
|
const effectiveModel = next.modelOverride ?? resolvedDefault.model;
|
|
264
249
|
if (!supportsXHighThinking(effectiveProvider, effectiveModel)) {
|
|
@@ -197,17 +197,21 @@ export const testState = {
|
|
|
197
197
|
export const testIsNixMode = hoisted.testIsNixMode;
|
|
198
198
|
export const sessionStoreSaveDelayMs = hoisted.sessionStoreSaveDelayMs;
|
|
199
199
|
export const embeddedRunMock = hoisted.embeddedRunMock;
|
|
200
|
-
vi.mock("
|
|
201
|
-
const actual = await vi.importActual("
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
discoverModels: (...args) => {
|
|
200
|
+
vi.mock("../agents/pi-model-discovery.js", async () => {
|
|
201
|
+
const actual = await vi.importActual("../agents/pi-model-discovery.js");
|
|
202
|
+
class MockModelRegistry extends actual.ModelRegistry {
|
|
203
|
+
getAll() {
|
|
205
204
|
if (!piSdkMock.enabled) {
|
|
206
|
-
return
|
|
205
|
+
return super.getAll();
|
|
207
206
|
}
|
|
208
207
|
piSdkMock.discoverCalls += 1;
|
|
208
|
+
// Cast to expected type for testing purposes
|
|
209
209
|
return piSdkMock.models;
|
|
210
|
-
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
...actual,
|
|
214
|
+
ModelRegistry: MockModelRegistry,
|
|
211
215
|
};
|
|
212
216
|
});
|
|
213
217
|
vi.mock("../cron/isolated-agent.js", () => ({
|
|
@@ -26,6 +26,7 @@ let previousConfigPath;
|
|
|
26
26
|
let previousSkipBrowserControl;
|
|
27
27
|
let previousSkipGmailWatcher;
|
|
28
28
|
let previousSkipCanvasHost;
|
|
29
|
+
let previousBundledPluginsDir;
|
|
29
30
|
let tempHome;
|
|
30
31
|
let tempConfigRoot;
|
|
31
32
|
export async function writeSessionStore(params) {
|
|
@@ -56,6 +57,7 @@ async function setupGatewayTestHome() {
|
|
|
56
57
|
previousSkipBrowserControl = process.env.CLAWDBOT_SKIP_BROWSER_CONTROL_SERVER;
|
|
57
58
|
previousSkipGmailWatcher = process.env.CLAWDBOT_SKIP_GMAIL_WATCHER;
|
|
58
59
|
previousSkipCanvasHost = process.env.CLAWDBOT_SKIP_CANVAS_HOST;
|
|
60
|
+
previousBundledPluginsDir = process.env.CLAWDBOT_BUNDLED_PLUGINS_DIR;
|
|
59
61
|
tempHome = await fs.mkdtemp(path.join(os.tmpdir(), "poolbot-gateway-home-"));
|
|
60
62
|
process.env.HOME = tempHome;
|
|
61
63
|
process.env.USERPROFILE = tempHome;
|
|
@@ -66,6 +68,9 @@ function applyGatewaySkipEnv() {
|
|
|
66
68
|
process.env.CLAWDBOT_SKIP_BROWSER_CONTROL_SERVER = "1";
|
|
67
69
|
process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = "1";
|
|
68
70
|
process.env.CLAWDBOT_SKIP_CANVAS_HOST = "1";
|
|
71
|
+
process.env.CLAWDBOT_BUNDLED_PLUGINS_DIR = tempHome
|
|
72
|
+
? path.join(tempHome, "poolbot-test-no-bundled-extensions")
|
|
73
|
+
: "poolbot-test-no-bundled-extensions";
|
|
69
74
|
}
|
|
70
75
|
async function resetGatewayTestState(options) {
|
|
71
76
|
// Some tests intentionally use fake timers; ensure they don't leak into gateway suites.
|
|
@@ -146,6 +151,10 @@ async function cleanupGatewayTestHome(options) {
|
|
|
146
151
|
delete process.env.CLAWDBOT_SKIP_CANVAS_HOST;
|
|
147
152
|
else
|
|
148
153
|
process.env.CLAWDBOT_SKIP_CANVAS_HOST = previousSkipCanvasHost;
|
|
154
|
+
if (previousBundledPluginsDir === undefined)
|
|
155
|
+
delete process.env.CLAWDBOT_BUNDLED_PLUGINS_DIR;
|
|
156
|
+
else
|
|
157
|
+
process.env.CLAWDBOT_BUNDLED_PLUGINS_DIR = previousBundledPluginsDir;
|
|
149
158
|
}
|
|
150
159
|
if (options.restoreEnv && tempHome) {
|
|
151
160
|
await fs.rm(tempHome, {
|
|
@@ -224,7 +233,8 @@ timeoutMs = 10_000) {
|
|
|
224
233
|
}
|
|
225
234
|
export async function startGatewayServer(port, opts) {
|
|
226
235
|
const mod = await serverModulePromise;
|
|
227
|
-
|
|
236
|
+
const resolvedOpts = opts?.controlUiEnabled === undefined ? { ...opts, controlUiEnabled: false } : opts;
|
|
237
|
+
return await mod.startGatewayServer(port, resolvedOpts);
|
|
228
238
|
}
|
|
229
239
|
export async function startServerWithClient(token, opts) {
|
|
230
240
|
let port = await getFreePort();
|
|
@@ -259,7 +269,30 @@ export async function startServerWithClient(token, opts) {
|
|
|
259
269
|
throw new Error("failed to start gateway server after retries");
|
|
260
270
|
}
|
|
261
271
|
const ws = new WebSocket(`ws://127.0.0.1:${port}`);
|
|
262
|
-
await new Promise((resolve) =>
|
|
272
|
+
await new Promise((resolve, reject) => {
|
|
273
|
+
const timer = setTimeout(() => reject(new Error("timeout waiting for ws open")), 10_000);
|
|
274
|
+
const cleanup = () => {
|
|
275
|
+
clearTimeout(timer);
|
|
276
|
+
ws.off("open", onOpen);
|
|
277
|
+
ws.off("error", onError);
|
|
278
|
+
ws.off("close", onClose);
|
|
279
|
+
};
|
|
280
|
+
const onOpen = () => {
|
|
281
|
+
cleanup();
|
|
282
|
+
resolve();
|
|
283
|
+
};
|
|
284
|
+
const onError = (err) => {
|
|
285
|
+
cleanup();
|
|
286
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
287
|
+
};
|
|
288
|
+
const onClose = (code, reason) => {
|
|
289
|
+
cleanup();
|
|
290
|
+
reject(new Error(`closed ${code}: ${reason.toString()}`));
|
|
291
|
+
};
|
|
292
|
+
ws.once("open", onOpen);
|
|
293
|
+
ws.once("error", onError);
|
|
294
|
+
ws.once("close", onClose);
|
|
295
|
+
});
|
|
263
296
|
return { server, ws, port, prevToken: prev };
|
|
264
297
|
}
|
|
265
298
|
export async function connectReq(ws, opts) {
|
|
@@ -4,7 +4,7 @@ import { resolveMarkdownTableMode } from "../../config/markdown-tables.js";
|
|
|
4
4
|
import { convertMarkdownTables } from "../../markdown/tables.js";
|
|
5
5
|
import { sendMessageIMessage } from "../send.js";
|
|
6
6
|
export async function deliverReplies(params) {
|
|
7
|
-
const { replies, target, client, runtime, maxBytes, textLimit, accountId } = params;
|
|
7
|
+
const { replies, target, client, runtime, maxBytes, textLimit, accountId, sentMessageCache } = params;
|
|
8
8
|
const cfg = loadConfig();
|
|
9
9
|
const tableMode = resolveMarkdownTableMode({
|
|
10
10
|
cfg,
|
|
@@ -25,6 +25,7 @@ export async function deliverReplies(params) {
|
|
|
25
25
|
client,
|
|
26
26
|
accountId,
|
|
27
27
|
});
|
|
28
|
+
sentMessageCache?.remember(target, chunk);
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
31
|
else {
|
|
@@ -38,6 +39,8 @@ export async function deliverReplies(params) {
|
|
|
38
39
|
client,
|
|
39
40
|
accountId,
|
|
40
41
|
});
|
|
42
|
+
if (caption)
|
|
43
|
+
sentMessageCache?.remember(target, caption);
|
|
41
44
|
}
|
|
42
45
|
}
|
|
43
46
|
runtime.log?.(`imessage: delivered reply to ${target}`);
|
|
@@ -25,6 +25,7 @@ import { truncateUtf16Safe } from "../../utils.js";
|
|
|
25
25
|
import { resolveControlCommandGate } from "../../channels/command-gating.js";
|
|
26
26
|
import { resolveIMessageAccount } from "../accounts.js";
|
|
27
27
|
import { createIMessageRpcClient } from "../client.js";
|
|
28
|
+
import { DEFAULT_IMESSAGE_PROBE_TIMEOUT_MS } from "../constants.js";
|
|
28
29
|
import { probeIMessage } from "../probe.js";
|
|
29
30
|
import { sendMessageIMessage } from "../send.js";
|
|
30
31
|
import { formatIMessageChatTarget, isAllowedIMessageSender, normalizeIMessageHandle, } from "../targets.js";
|
|
@@ -72,6 +73,38 @@ function describeReplyContext(message) {
|
|
|
72
73
|
const sender = normalizeReplyField(message.reply_to_sender);
|
|
73
74
|
return { body, id, sender };
|
|
74
75
|
}
|
|
76
|
+
class SentMessageCache {
|
|
77
|
+
cache = new Map();
|
|
78
|
+
ttlMs = 5000;
|
|
79
|
+
remember(scope, text) {
|
|
80
|
+
if (!text?.trim())
|
|
81
|
+
return;
|
|
82
|
+
const key = `${scope}:${text.trim()}`;
|
|
83
|
+
this.cache.set(key, Date.now());
|
|
84
|
+
this.cleanup();
|
|
85
|
+
}
|
|
86
|
+
has(scope, text) {
|
|
87
|
+
if (!text?.trim())
|
|
88
|
+
return false;
|
|
89
|
+
const key = `${scope}:${text.trim()}`;
|
|
90
|
+
const timestamp = this.cache.get(key);
|
|
91
|
+
if (!timestamp)
|
|
92
|
+
return false;
|
|
93
|
+
const age = Date.now() - timestamp;
|
|
94
|
+
if (age > this.ttlMs) {
|
|
95
|
+
this.cache.delete(key);
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
cleanup() {
|
|
101
|
+
const now = Date.now();
|
|
102
|
+
for (const [text, timestamp] of this.cache.entries()) {
|
|
103
|
+
if (now - timestamp > this.ttlMs)
|
|
104
|
+
this.cache.delete(text);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
75
108
|
export async function monitorIMessageProvider(opts = {}) {
|
|
76
109
|
const runtime = resolveRuntime(opts);
|
|
77
110
|
const cfg = opts.config ?? loadConfig();
|
|
@@ -84,6 +117,7 @@ export async function monitorIMessageProvider(opts = {}) {
|
|
|
84
117
|
cfg.messages?.groupChat?.historyLimit ??
|
|
85
118
|
DEFAULT_GROUP_HISTORY_LIMIT);
|
|
86
119
|
const groupHistories = new Map();
|
|
120
|
+
const sentMessageCache = new SentMessageCache();
|
|
87
121
|
const textLimit = resolveTextChunkLimit(cfg, "imessage", accountInfo.accountId);
|
|
88
122
|
const allowFrom = normalizeAllowList(opts.allowFrom ?? imessageCfg.allowFrom);
|
|
89
123
|
const groupAllowFrom = normalizeAllowList(opts.groupAllowFrom ??
|
|
@@ -96,6 +130,7 @@ export async function monitorIMessageProvider(opts = {}) {
|
|
|
96
130
|
const mediaMaxBytes = (opts.mediaMaxMb ?? imessageCfg.mediaMaxMb ?? 16) * 1024 * 1024;
|
|
97
131
|
const cliPath = opts.cliPath ?? imessageCfg.cliPath ?? "imsg";
|
|
98
132
|
const dbPath = opts.dbPath ?? imessageCfg.dbPath;
|
|
133
|
+
const probeTimeoutMs = imessageCfg.probeTimeoutMs ?? DEFAULT_IMESSAGE_PROBE_TIMEOUT_MS;
|
|
99
134
|
// Resolve remoteHost: explicit config, or auto-detect from SSH wrapper script
|
|
100
135
|
let remoteHost = imessageCfg.remoteHost;
|
|
101
136
|
if (!remoteHost && cliPath && cliPath !== "imsg") {
|
|
@@ -290,6 +325,11 @@ export async function monitorIMessageProvider(opts = {}) {
|
|
|
290
325
|
});
|
|
291
326
|
const mentionRegexes = buildMentionRegexes(cfg, route.agentId);
|
|
292
327
|
const messageText = (message.text ?? "").trim();
|
|
328
|
+
const echoScope = `${accountInfo.accountId}:${isGroup ? formatIMessageChatTarget(chatId) : `imessage:${sender}`}`;
|
|
329
|
+
if (messageText && sentMessageCache.has(echoScope, messageText)) {
|
|
330
|
+
logVerbose(`imessage: skipping echo message (matches recently sent text within 5s): "${truncateUtf16Safe(messageText, 50)}"`);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
293
333
|
const attachments = includeAttachments ? (message.attachments ?? []) : [];
|
|
294
334
|
// Filter to valid attachments with paths
|
|
295
335
|
const validAttachments = attachments.filter((entry) => entry?.original_path && !entry?.missing);
|
|
@@ -426,8 +466,17 @@ export async function monitorIMessageProvider(opts = {}) {
|
|
|
426
466
|
});
|
|
427
467
|
}
|
|
428
468
|
const imessageTo = (isGroup ? chatTarget : undefined) || `imessage:${sender}`;
|
|
469
|
+
const inboundHistory = isGroup && historyKey && historyLimit > 0
|
|
470
|
+
? (groupHistories.get(historyKey) ?? []).map((entry) => ({
|
|
471
|
+
sender: entry.sender,
|
|
472
|
+
body: entry.body,
|
|
473
|
+
timestamp: entry.timestamp,
|
|
474
|
+
}))
|
|
475
|
+
: undefined;
|
|
429
476
|
const ctxPayload = finalizeInboundContext({
|
|
430
477
|
Body: combinedBody,
|
|
478
|
+
BodyForAgent: bodyText,
|
|
479
|
+
InboundHistory: inboundHistory,
|
|
431
480
|
RawBody: bodyText,
|
|
432
481
|
CommandBody: bodyText,
|
|
433
482
|
From: isGroup ? `imessage:group:${chatId ?? "unknown"}` : `imessage:${sender}`,
|
|
@@ -495,6 +544,7 @@ export async function monitorIMessageProvider(opts = {}) {
|
|
|
495
544
|
runtime,
|
|
496
545
|
maxBytes: mediaMaxBytes,
|
|
497
546
|
textLimit,
|
|
547
|
+
sentMessageCache,
|
|
498
548
|
});
|
|
499
549
|
},
|
|
500
550
|
onError: (err, info) => {
|
|
@@ -542,7 +592,7 @@ export async function monitorIMessageProvider(opts = {}) {
|
|
|
542
592
|
abortSignal: opts.abortSignal,
|
|
543
593
|
runtime,
|
|
544
594
|
check: async () => {
|
|
545
|
-
const probe = await probeIMessage(
|
|
595
|
+
const probe = await probeIMessage(probeTimeoutMs, { cliPath, dbPath, runtime });
|
|
546
596
|
if (probe.ok)
|
|
547
597
|
return { ok: true };
|
|
548
598
|
if (probe.fatal) {
|