@poolzin/pool-bot 2026.2.23 → 2026.2.25
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 +29 -0
- package/dist/acp/client.js +207 -18
- package/dist/acp/secret-file.js +22 -0
- package/dist/agents/agent-scope.js +10 -0
- package/dist/agents/bash-process-registry.test-helpers.js +29 -0
- package/dist/agents/bash-tools.exec-approval-request.js +20 -0
- package/dist/agents/bash-tools.exec-host-gateway.js +230 -0
- package/dist/agents/bash-tools.exec-host-node.js +235 -0
- package/dist/agents/bash-tools.exec-types.js +1 -0
- package/dist/agents/bash-tools.process.js +224 -218
- package/dist/agents/content-blocks.js +16 -0
- package/dist/agents/model-fallback.js +96 -101
- package/dist/agents/models-config.providers.js +299 -182
- package/dist/agents/pi-embedded-payloads.js +1 -0
- package/dist/agents/pi-embedded-runner/run.overflow-compaction.fixture.js +34 -0
- package/dist/agents/skills.test-helpers.js +13 -0
- package/dist/agents/stable-stringify.js +12 -0
- package/dist/agents/subagent-registry.mocks.shared.js +12 -0
- package/dist/agents/test-helpers/assistant-message-fixtures.js +29 -0
- package/dist/agents/test-helpers/pi-tools-sandbox-context.js +27 -0
- package/dist/agents/tool-policy-shared.js +108 -0
- package/dist/agents/tools/browser-tool.js +160 -54
- package/dist/agents/tools/cron-tool.test-helpers.js +12 -0
- package/dist/agents/tools/discord-actions-moderation-shared.js +27 -0
- package/dist/agents/tools/image-tool.js +214 -99
- package/dist/agents/tools/sessions-history-tool.js +140 -108
- package/dist/agents/workspace.js +222 -46
- package/dist/auto-reply/commands-registry.js +15 -18
- package/dist/auto-reply/fallback-state.js +114 -0
- package/dist/auto-reply/model-runtime.js +68 -0
- package/dist/auto-reply/reply/agent-runner-execution.js +36 -4
- package/dist/auto-reply/reply/agent-runner.js +165 -39
- package/dist/auto-reply/reply/commands-setunset-standard.js +13 -0
- package/dist/browser/config.js +26 -0
- package/dist/browser/navigation-guard.js +31 -0
- package/dist/browser/routes/agent.act.js +431 -424
- package/dist/browser/routes/agent.shared.js +47 -3
- package/dist/browser/routes/agent.snapshot.js +122 -116
- package/dist/browser/routes/agent.storage.js +303 -297
- package/dist/browser/routes/tabs.js +154 -100
- package/dist/browser/server-lifecycle.js +37 -0
- package/dist/build-info.json +3 -3
- package/dist/channels/allow-from.js +25 -0
- package/dist/channels/plugins/account-action-gate.js +13 -0
- package/dist/channels/plugins/message-actions.js +10 -0
- package/dist/channels/telegram/api.js +18 -0
- package/dist/cli/argv.js +84 -21
- package/dist/cli/banner.js +2 -1
- package/dist/cli/exec-approvals-cli.js +92 -124
- package/dist/cli/memory-cli.js +158 -61
- package/dist/cli/nodes-cli/register.push.js +63 -0
- package/dist/cli/nodes-media-utils.js +21 -0
- package/dist/cli/plugins-cli.js +245 -61
- package/dist/cli/program/build-program.js +3 -1
- package/dist/cli/program/command-registry.js +223 -136
- package/dist/cli/program/help.js +43 -12
- package/dist/cli/route.js +1 -1
- package/dist/cli/test-runtime-capture.js +24 -0
- package/dist/commands/agent.js +163 -87
- package/dist/commands/channels.mock-harness.js +23 -0
- package/dist/commands/daemon-install-runtime-warning.js +11 -0
- package/dist/commands/onboard-helpers.js +4 -4
- package/dist/commands/sessions.test-helpers.js +61 -0
- package/dist/compat/legacy-names.js +2 -2
- package/dist/config/commands.js +3 -0
- package/dist/config/config.js +1 -1
- package/dist/config/env-substitution.js +62 -34
- package/dist/config/env-vars.js +9 -0
- package/dist/config/io.js +571 -171
- package/dist/config/merge-patch.js +50 -4
- package/dist/config/redact-snapshot.js +404 -76
- package/dist/config/schema.js +58 -570
- package/dist/config/validation.js +140 -85
- package/dist/config/zod-schema.hooks.js +40 -11
- package/dist/config/zod-schema.installs.js +20 -0
- package/dist/config/zod-schema.js +8 -7
- package/dist/control-ui/assets/{index-HRr1grwl.js → index-Dvkl4Xlx.js} +2 -1
- package/dist/control-ui/assets/{index-HRr1grwl.js.map → index-Dvkl4Xlx.js.map} +1 -1
- package/dist/control-ui/index.html +1 -1
- package/dist/daemon/cmd-argv.js +21 -0
- package/dist/daemon/cmd-set.js +58 -0
- package/dist/daemon/service-types.js +1 -0
- package/dist/discord/monitor/exec-approvals.js +357 -162
- package/dist/gateway/auth.js +38 -3
- package/dist/gateway/call.js +149 -68
- package/dist/gateway/canvas-capability.js +75 -0
- package/dist/gateway/control-plane-audit.js +28 -0
- package/dist/gateway/control-plane-rate-limit.js +53 -0
- package/dist/gateway/events.js +1 -0
- package/dist/gateway/hooks.js +109 -54
- package/dist/gateway/http-common.js +22 -0
- package/dist/gateway/method-scopes.js +169 -0
- package/dist/gateway/net.js +23 -0
- package/dist/gateway/openresponses-http.js +120 -110
- package/dist/gateway/probe-auth.js +2 -0
- package/dist/gateway/protocol/index.js +3 -2
- package/dist/gateway/protocol/schema/protocol-schemas.js +2 -0
- package/dist/gateway/protocol/schema/push.js +18 -0
- package/dist/gateway/protocol/schema.js +1 -0
- package/dist/gateway/server-http.js +236 -52
- package/dist/gateway/server-methods/agent.js +162 -24
- package/dist/gateway/server-methods/chat.js +461 -130
- package/dist/gateway/server-methods/config.js +193 -150
- package/dist/gateway/server-methods/nodes.helpers.js +12 -0
- package/dist/gateway/server-methods/nodes.js +251 -69
- package/dist/gateway/server-methods/push.js +53 -0
- package/dist/gateway/server-reload-handlers.js +2 -3
- package/dist/gateway/server-runtime-config.js +5 -0
- package/dist/gateway/server-runtime-state.js +2 -0
- package/dist/gateway/server-ws-runtime.js +1 -0
- package/dist/gateway/server.impl.js +296 -139
- package/dist/gateway/session-preview.test-helpers.js +11 -0
- package/dist/gateway/startup-auth.js +126 -0
- package/dist/gateway/test-helpers.agent-results.js +15 -0
- package/dist/gateway/test-helpers.mocks.js +37 -14
- package/dist/gateway/test-helpers.server.js +161 -77
- package/dist/hooks/bundled/session-memory/handler.js +165 -34
- package/dist/hooks/gmail-watcher-lifecycle.js +23 -0
- package/dist/infra/archive-path.js +49 -0
- package/dist/infra/device-pairing.js +148 -167
- package/dist/infra/exec-approvals-allowlist.js +19 -70
- package/dist/infra/exec-approvals-analysis.js +44 -17
- package/dist/infra/exec-safe-bin-policy.js +269 -0
- package/dist/infra/fixed-window-rate-limit.js +33 -0
- package/dist/infra/git-root.js +61 -0
- package/dist/infra/heartbeat-active-hours.js +2 -2
- package/dist/infra/heartbeat-reason.js +40 -0
- package/dist/infra/heartbeat-runner.js +72 -32
- package/dist/infra/install-source-utils.js +91 -7
- package/dist/infra/node-pairing.js +50 -105
- package/dist/infra/npm-integrity.js +45 -0
- package/dist/infra/npm-pack-install.js +40 -0
- package/dist/infra/outbound/channel-adapters.js +20 -7
- package/dist/infra/outbound/message-action-runner.js +107 -327
- package/dist/infra/outbound/message.js +59 -36
- package/dist/infra/outbound/outbound-policy.js +52 -25
- package/dist/infra/outbound/outbound-send-service.js +58 -71
- package/dist/infra/pairing-files.js +10 -0
- package/dist/infra/plain-object.js +9 -0
- package/dist/infra/push-apns.js +365 -0
- package/dist/infra/restart-sentinel.js +16 -1
- package/dist/infra/restart.js +229 -26
- package/dist/infra/scp-host.js +54 -0
- package/dist/infra/update-startup.js +86 -9
- package/dist/media/inbound-path-policy.js +114 -0
- package/dist/media/input-files.js +16 -0
- package/dist/memory/test-manager.js +8 -0
- package/dist/plugin-sdk/temp-path.js +47 -0
- package/dist/plugins/discovery.js +217 -23
- package/dist/plugins/hook-runner-global.js +16 -0
- package/dist/plugins/loader.js +192 -26
- package/dist/plugins/logger.js +8 -0
- package/dist/plugins/manifest-registry.js +3 -0
- package/dist/plugins/path-safety.js +34 -0
- package/dist/plugins/registry.js +5 -2
- package/dist/plugins/runtime/index.js +271 -206
- package/dist/providers/github-copilot-models.js +4 -1
- package/dist/security/audit-channel.js +8 -19
- package/dist/security/audit-extra.async.js +354 -182
- package/dist/security/audit-extra.js +11 -1
- package/dist/security/audit-extra.sync.js +340 -33
- package/dist/security/audit-fs.js +31 -13
- package/dist/security/audit.js +145 -371
- package/dist/security/dm-policy-shared.js +24 -0
- package/dist/security/external-content.js +20 -8
- package/dist/security/fix.js +49 -85
- package/dist/security/scan-paths.js +20 -0
- package/dist/security/secret-equal.js +3 -7
- package/dist/security/windows-acl.js +30 -15
- package/dist/shared/node-list-parse.js +13 -0
- package/dist/shared/operator-scope-compat.js +37 -0
- package/dist/shared/text-chunking.js +29 -0
- package/dist/slack/blocks.test-helpers.js +31 -0
- package/dist/slack/monitor/mrkdwn.js +8 -0
- package/dist/telegram/bot-message-dispatch.js +366 -164
- package/dist/telegram/draft-stream.js +30 -7
- package/dist/telegram/reasoning-lane-coordinator.js +128 -0
- package/dist/terminal/prompt-select-styled.js +9 -0
- package/dist/test-utils/command-runner.js +6 -0
- package/dist/test-utils/internal-hook-event-payload.js +10 -0
- package/dist/test-utils/model-auth-mock.js +12 -0
- package/dist/test-utils/provider-usage-fetch.js +14 -0
- package/dist/test-utils/temp-home.js +33 -0
- package/dist/tui/components/chat-log.js +9 -0
- package/dist/tui/tui-command-handlers.js +36 -27
- package/dist/tui/tui-event-handlers.js +122 -32
- package/dist/tui/tui.js +181 -45
- package/dist/utils/mask-api-key.js +10 -0
- package/dist/utils/run-with-concurrency.js +39 -0
- package/dist/web/media.js +4 -0
- package/docs/tools/slash-commands.md +5 -1
- package/extensions/bluebubbles/package.json +1 -1
- package/extensions/copilot-proxy/package.json +1 -1
- package/extensions/diagnostics-otel/package.json +1 -1
- package/extensions/discord/package.json +1 -1
- package/extensions/feishu/package.json +1 -1
- package/extensions/feishu/src/external-keys.ts +19 -0
- package/extensions/google-antigravity-auth/package.json +1 -1
- package/extensions/google-gemini-cli-auth/package.json +1 -1
- package/extensions/googlechat/package.json +1 -1
- package/extensions/imessage/package.json +1 -1
- package/extensions/irc/package.json +1 -1
- package/extensions/line/package.json +1 -1
- package/extensions/llm-task/package.json +1 -1
- package/extensions/lobster/package.json +1 -1
- package/extensions/lobster/src/windows-spawn.ts +193 -0
- package/extensions/matrix/CHANGELOG.md +5 -0
- package/extensions/matrix/package.json +1 -1
- package/extensions/matrix/src/matrix/actions/limits.ts +6 -0
- package/extensions/mattermost/package.json +1 -1
- package/extensions/mattermost/src/mattermost/reactions.test-helpers.ts +83 -0
- package/extensions/memory-core/package.json +1 -1
- package/extensions/memory-lancedb/package.json +1 -1
- package/extensions/minimax-portal-auth/package.json +1 -1
- package/extensions/msteams/CHANGELOG.md +5 -0
- package/extensions/msteams/package.json +1 -1
- package/extensions/nextcloud-talk/package.json +1 -1
- package/extensions/nostr/CHANGELOG.md +5 -0
- package/extensions/nostr/package.json +1 -1
- package/extensions/open-prose/package.json +1 -1
- package/extensions/openai-codex-auth/package.json +1 -1
- package/extensions/signal/package.json +1 -1
- package/extensions/slack/package.json +1 -1
- package/extensions/telegram/package.json +1 -1
- package/extensions/tlon/package.json +1 -1
- package/extensions/twitch/CHANGELOG.md +5 -0
- package/extensions/twitch/package.json +1 -1
- package/extensions/voice-call/CHANGELOG.md +5 -0
- package/extensions/voice-call/package.json +1 -1
- package/extensions/whatsapp/package.json +1 -1
- package/extensions/zalo/CHANGELOG.md +5 -0
- package/extensions/zalo/package.json +1 -1
- package/extensions/zalouser/CHANGELOG.md +5 -0
- package/extensions/zalouser/package.json +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { writeConfigFile } from "../config/config.js";
|
|
3
|
+
import { resolveGatewayAuth } from "./auth.js";
|
|
4
|
+
export function mergeGatewayAuthConfig(base, override) {
|
|
5
|
+
const merged = { ...base };
|
|
6
|
+
if (!override) {
|
|
7
|
+
return merged;
|
|
8
|
+
}
|
|
9
|
+
if (override.mode !== undefined) {
|
|
10
|
+
merged.mode = override.mode;
|
|
11
|
+
}
|
|
12
|
+
if (override.token !== undefined) {
|
|
13
|
+
merged.token = override.token;
|
|
14
|
+
}
|
|
15
|
+
if (override.password !== undefined) {
|
|
16
|
+
merged.password = override.password;
|
|
17
|
+
}
|
|
18
|
+
if (override.allowTailscale !== undefined) {
|
|
19
|
+
merged.allowTailscale = override.allowTailscale;
|
|
20
|
+
}
|
|
21
|
+
if (override.rateLimit !== undefined) {
|
|
22
|
+
merged.rateLimit = override.rateLimit;
|
|
23
|
+
}
|
|
24
|
+
if (override.trustedProxy !== undefined) {
|
|
25
|
+
merged.trustedProxy = override.trustedProxy;
|
|
26
|
+
}
|
|
27
|
+
return merged;
|
|
28
|
+
}
|
|
29
|
+
export function mergeGatewayTailscaleConfig(base, override) {
|
|
30
|
+
const merged = { ...base };
|
|
31
|
+
if (!override) {
|
|
32
|
+
return merged;
|
|
33
|
+
}
|
|
34
|
+
if (override.mode !== undefined) {
|
|
35
|
+
merged.mode = override.mode;
|
|
36
|
+
}
|
|
37
|
+
if (override.resetOnExit !== undefined) {
|
|
38
|
+
merged.resetOnExit = override.resetOnExit;
|
|
39
|
+
}
|
|
40
|
+
return merged;
|
|
41
|
+
}
|
|
42
|
+
function resolveGatewayAuthFromConfig(params) {
|
|
43
|
+
const tailscaleConfig = mergeGatewayTailscaleConfig(params.cfg.gateway?.tailscale, params.tailscaleOverride);
|
|
44
|
+
return resolveGatewayAuth({
|
|
45
|
+
authConfig: params.cfg.gateway?.auth,
|
|
46
|
+
authOverride: params.authOverride,
|
|
47
|
+
env: params.env,
|
|
48
|
+
tailscaleMode: tailscaleConfig.mode ?? "off",
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function shouldPersistGeneratedToken(params) {
|
|
52
|
+
if (!params.persistRequested) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
// Keep CLI/runtime mode overrides ephemeral: startup should not silently
|
|
56
|
+
// mutate durable auth policy when mode was chosen by an override flag.
|
|
57
|
+
if (params.resolvedAuth.modeSource === "override") {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
export async function ensureGatewayStartupAuth(params) {
|
|
63
|
+
const env = params.env ?? process.env;
|
|
64
|
+
const persistRequested = params.persist === true;
|
|
65
|
+
const resolved = resolveGatewayAuthFromConfig({
|
|
66
|
+
cfg: params.cfg,
|
|
67
|
+
env,
|
|
68
|
+
authOverride: params.authOverride,
|
|
69
|
+
tailscaleOverride: params.tailscaleOverride,
|
|
70
|
+
});
|
|
71
|
+
if (resolved.mode !== "token" || (resolved.token?.trim().length ?? 0) > 0) {
|
|
72
|
+
assertHooksTokenSeparateFromGatewayAuth({ cfg: params.cfg, auth: resolved });
|
|
73
|
+
return { cfg: params.cfg, auth: resolved, persistedGeneratedToken: false };
|
|
74
|
+
}
|
|
75
|
+
const generatedToken = crypto.randomBytes(24).toString("hex");
|
|
76
|
+
const nextCfg = {
|
|
77
|
+
...params.cfg,
|
|
78
|
+
gateway: {
|
|
79
|
+
...params.cfg.gateway,
|
|
80
|
+
auth: {
|
|
81
|
+
...params.cfg.gateway?.auth,
|
|
82
|
+
mode: "token",
|
|
83
|
+
token: generatedToken,
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
const persist = shouldPersistGeneratedToken({
|
|
88
|
+
persistRequested,
|
|
89
|
+
resolvedAuth: resolved,
|
|
90
|
+
});
|
|
91
|
+
if (persist) {
|
|
92
|
+
await writeConfigFile(nextCfg);
|
|
93
|
+
}
|
|
94
|
+
const nextAuth = resolveGatewayAuthFromConfig({
|
|
95
|
+
cfg: nextCfg,
|
|
96
|
+
env,
|
|
97
|
+
authOverride: params.authOverride,
|
|
98
|
+
tailscaleOverride: params.tailscaleOverride,
|
|
99
|
+
});
|
|
100
|
+
assertHooksTokenSeparateFromGatewayAuth({ cfg: nextCfg, auth: nextAuth });
|
|
101
|
+
return {
|
|
102
|
+
cfg: nextCfg,
|
|
103
|
+
auth: nextAuth,
|
|
104
|
+
generatedToken,
|
|
105
|
+
persistedGeneratedToken: persist,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
export function assertHooksTokenSeparateFromGatewayAuth(params) {
|
|
109
|
+
if (params.cfg.hooks?.enabled !== true) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const hooksToken = typeof params.cfg.hooks.token === "string" ? params.cfg.hooks.token.trim() : "";
|
|
113
|
+
if (!hooksToken) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const gatewayToken = params.auth.mode === "token" && typeof params.auth.token === "string"
|
|
117
|
+
? params.auth.token.trim()
|
|
118
|
+
: "";
|
|
119
|
+
if (!gatewayToken) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (hooksToken !== gatewayToken) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
throw new Error("Invalid config: hooks.token must not match gateway auth token. Set a distinct hooks.token for hook ingress.");
|
|
126
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function extractPayloadText(result) {
|
|
2
|
+
const record = result;
|
|
3
|
+
const payloads = Array.isArray(record.payloads) ? record.payloads : [];
|
|
4
|
+
const texts = payloads
|
|
5
|
+
.map((p) => (p && typeof p === "object" ? p.text : undefined))
|
|
6
|
+
.filter((t) => typeof t === "string" && t.trim().length > 0);
|
|
7
|
+
return texts.join("\n").trim();
|
|
8
|
+
}
|
|
9
|
+
export function buildAssistantDeltaResult(params) {
|
|
10
|
+
const runId = params.opts?.runId ?? "";
|
|
11
|
+
for (const delta of params.deltas) {
|
|
12
|
+
params.emit({ runId, stream: "assistant", data: { delta } });
|
|
13
|
+
}
|
|
14
|
+
return { payloads: [{ text: params.text }] };
|
|
15
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
|
-
import fs from "node:fs/promises";
|
|
3
2
|
import fsSync from "node:fs";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { vi } from "vitest";
|
|
@@ -147,6 +147,7 @@ const hoisted = vi.hoisted(() => ({
|
|
|
147
147
|
waitCalls: [],
|
|
148
148
|
waitResults: new Map(),
|
|
149
149
|
},
|
|
150
|
+
testTailscaleWhois: { value: null },
|
|
150
151
|
getReplyFromConfig: vi.fn().mockResolvedValue(undefined),
|
|
151
152
|
sendWhatsAppMock: vi.fn().mockResolvedValue({ messageId: "msg-1", toJid: "jid-1" }),
|
|
152
153
|
}));
|
|
@@ -168,9 +169,9 @@ const testConfigRoot = {
|
|
|
168
169
|
export const setTestConfigRoot = (root) => {
|
|
169
170
|
testConfigRoot.value = root;
|
|
170
171
|
process.env.POOLBOT_CONFIG_PATH = path.join(root, "poolbot.json");
|
|
171
|
-
process.env.CLAWDBOT_CONFIG_PATH = path.join(root, "poolbot.json");
|
|
172
172
|
};
|
|
173
173
|
export const testTailnetIPv4 = hoisted.testTailnetIPv4;
|
|
174
|
+
export const testTailscaleWhois = hoisted.testTailscaleWhois;
|
|
174
175
|
export const piSdkMock = hoisted.piSdkMock;
|
|
175
176
|
export const cronIsolatedRun = hoisted.cronIsolatedRun;
|
|
176
177
|
export const agentCommand = hoisted.agentCommand;
|
|
@@ -222,6 +223,13 @@ vi.mock("../infra/tailnet.js", () => ({
|
|
|
222
223
|
pickPrimaryTailnetIPv4: () => testTailnetIPv4.value,
|
|
223
224
|
pickPrimaryTailnetIPv6: () => undefined,
|
|
224
225
|
}));
|
|
226
|
+
vi.mock("../infra/tailscale.js", async () => {
|
|
227
|
+
const actual = await vi.importActual("../infra/tailscale.js");
|
|
228
|
+
return {
|
|
229
|
+
...actual,
|
|
230
|
+
readTailscaleWhoisIdentity: async () => testTailscaleWhois.value,
|
|
231
|
+
};
|
|
232
|
+
});
|
|
225
233
|
vi.mock("../config/sessions.js", async () => {
|
|
226
234
|
const actual = await vi.importActual("../config/sessions.js");
|
|
227
235
|
return {
|
|
@@ -350,8 +358,8 @@ vi.mock("../config/config.js", async () => {
|
|
|
350
358
|
? fileAgents.defaults
|
|
351
359
|
: {};
|
|
352
360
|
const defaults = {
|
|
353
|
-
model: { primary: "anthropic/claude-opus-4-
|
|
354
|
-
workspace: path.join(os.tmpdir(), "
|
|
361
|
+
model: { primary: "anthropic/claude-opus-4-6" },
|
|
362
|
+
workspace: path.join(os.tmpdir(), "poolbot-gateway-test"),
|
|
355
363
|
...fileDefaults,
|
|
356
364
|
...testState.agentConfig,
|
|
357
365
|
};
|
|
@@ -391,38 +399,46 @@ vi.mock("../config/config.js", async () => {
|
|
|
391
399
|
...fileSession,
|
|
392
400
|
mainKey: fileSession.mainKey ?? "main",
|
|
393
401
|
};
|
|
394
|
-
if (typeof testState.sessionStorePath === "string")
|
|
402
|
+
if (typeof testState.sessionStorePath === "string") {
|
|
395
403
|
session.store = testState.sessionStorePath;
|
|
396
|
-
|
|
404
|
+
}
|
|
405
|
+
if (testState.sessionConfig) {
|
|
397
406
|
Object.assign(session, testState.sessionConfig);
|
|
407
|
+
}
|
|
398
408
|
const fileGateway = fileConfig.gateway &&
|
|
399
409
|
typeof fileConfig.gateway === "object" &&
|
|
400
410
|
!Array.isArray(fileConfig.gateway)
|
|
401
411
|
? { ...fileConfig.gateway }
|
|
402
412
|
: {};
|
|
403
|
-
if (testState.gatewayBind)
|
|
413
|
+
if (testState.gatewayBind) {
|
|
404
414
|
fileGateway.bind = testState.gatewayBind;
|
|
405
|
-
|
|
415
|
+
}
|
|
416
|
+
if (testState.gatewayAuth) {
|
|
406
417
|
fileGateway.auth = testState.gatewayAuth;
|
|
407
|
-
|
|
418
|
+
}
|
|
419
|
+
if (testState.gatewayControlUi) {
|
|
408
420
|
fileGateway.controlUi = testState.gatewayControlUi;
|
|
421
|
+
}
|
|
409
422
|
const gateway = Object.keys(fileGateway).length > 0 ? fileGateway : undefined;
|
|
410
423
|
const fileCanvasHost = fileConfig.canvasHost &&
|
|
411
424
|
typeof fileConfig.canvasHost === "object" &&
|
|
412
425
|
!Array.isArray(fileConfig.canvasHost)
|
|
413
426
|
? { ...fileConfig.canvasHost }
|
|
414
427
|
: {};
|
|
415
|
-
if (typeof testState.canvasHostPort === "number")
|
|
428
|
+
if (typeof testState.canvasHostPort === "number") {
|
|
416
429
|
fileCanvasHost.port = testState.canvasHostPort;
|
|
430
|
+
}
|
|
417
431
|
const canvasHost = Object.keys(fileCanvasHost).length > 0 ? fileCanvasHost : undefined;
|
|
418
432
|
const hooks = testState.hooksConfig ?? fileConfig.hooks;
|
|
419
433
|
const fileCron = fileConfig.cron && typeof fileConfig.cron === "object" && !Array.isArray(fileConfig.cron)
|
|
420
434
|
? { ...fileConfig.cron }
|
|
421
435
|
: {};
|
|
422
|
-
if (typeof testState.cronEnabled === "boolean")
|
|
436
|
+
if (typeof testState.cronEnabled === "boolean") {
|
|
423
437
|
fileCron.enabled = testState.cronEnabled;
|
|
424
|
-
|
|
438
|
+
}
|
|
439
|
+
if (typeof testState.cronStorePath === "string") {
|
|
425
440
|
fileCron.store = testState.cronStorePath;
|
|
441
|
+
}
|
|
426
442
|
const cron = Object.keys(fileCron).length > 0 ? fileCron : undefined;
|
|
427
443
|
const config = {
|
|
428
444
|
...fileConfig,
|
|
@@ -503,7 +519,14 @@ vi.mock("../cli/deps.js", async () => {
|
|
|
503
519
|
}),
|
|
504
520
|
};
|
|
505
521
|
});
|
|
522
|
+
vi.mock("../plugins/loader.js", async () => {
|
|
523
|
+
const actual = await vi.importActual("../plugins/loader.js");
|
|
524
|
+
return {
|
|
525
|
+
...actual,
|
|
526
|
+
loadPoolBotPlugins: () => pluginRegistryState.registry,
|
|
527
|
+
};
|
|
528
|
+
});
|
|
529
|
+
process.env.POOLBOT_SKIP_CHANNELS = "1";
|
|
530
|
+
process.env.POOLBOT_SKIP_CRON = "1";
|
|
506
531
|
process.env.POOLBOT_SKIP_CHANNELS = "1";
|
|
507
|
-
process.env.CLAWDBOT_SKIP_CHANNELS = "1";
|
|
508
532
|
process.env.POOLBOT_SKIP_CRON = "1";
|
|
509
|
-
process.env.CLAWDBOT_SKIP_CRON = "1";
|
|
@@ -11,15 +11,19 @@ import { drainSystemEvents, peekSystemEvents } from "../infra/system-events.js";
|
|
|
11
11
|
import { rawDataToString } from "../infra/ws.js";
|
|
12
12
|
import { resetLogger, setLoggerOverride } from "../logging.js";
|
|
13
13
|
import { DEFAULT_AGENT_ID, toAgentStoreSessionKey } from "../routing/session-key.js";
|
|
14
|
-
import { getDeterministicFreePortBlock } from "../test-utils/ports.js";
|
|
15
14
|
import { captureEnv } from "../test-utils/env.js";
|
|
15
|
+
import { getDeterministicFreePortBlock } from "../test-utils/ports.js";
|
|
16
16
|
import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-channel.js";
|
|
17
|
-
import { PROTOCOL_VERSION } from "./protocol/index.js";
|
|
18
17
|
import { buildDeviceAuthPayload } from "./device-auth.js";
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
//
|
|
22
|
-
|
|
18
|
+
import { PROTOCOL_VERSION } from "./protocol/index.js";
|
|
19
|
+
import { agentCommand, cronIsolatedRun, embeddedRunMock, piSdkMock, sessionStoreSaveDelayMs, setTestConfigRoot, testIsNixMode, testTailscaleWhois, testState, testTailnetIPv4, } from "./test-helpers.mocks.js";
|
|
20
|
+
// Import lazily after test env/home setup so config/session paths resolve to test dirs.
|
|
21
|
+
// Keep one cached module per worker for speed.
|
|
22
|
+
let serverModulePromise;
|
|
23
|
+
async function getServerModule() {
|
|
24
|
+
serverModulePromise ??= import("./server.js");
|
|
25
|
+
return await serverModulePromise;
|
|
26
|
+
}
|
|
23
27
|
let previousHome;
|
|
24
28
|
let previousUserProfile;
|
|
25
29
|
let previousStateDir;
|
|
@@ -28,12 +32,17 @@ let previousSkipBrowserControl;
|
|
|
28
32
|
let previousSkipGmailWatcher;
|
|
29
33
|
let previousSkipCanvasHost;
|
|
30
34
|
let previousBundledPluginsDir;
|
|
35
|
+
let previousSkipChannels;
|
|
36
|
+
let previousSkipProviders;
|
|
37
|
+
let previousSkipCron;
|
|
38
|
+
let previousMinimalGateway;
|
|
31
39
|
let tempHome;
|
|
32
40
|
let tempConfigRoot;
|
|
33
41
|
export async function writeSessionStore(params) {
|
|
34
42
|
const storePath = params.storePath ?? testState.sessionStorePath;
|
|
35
|
-
if (!storePath)
|
|
43
|
+
if (!storePath) {
|
|
36
44
|
throw new Error("writeSessionStore requires testState.sessionStorePath");
|
|
45
|
+
}
|
|
37
46
|
const agentId = params.agentId ?? DEFAULT_AGENT_ID;
|
|
38
47
|
const store = {};
|
|
39
48
|
for (const [requestKey, entry] of Object.entries(params.entries)) {
|
|
@@ -53,37 +62,33 @@ export async function writeSessionStore(params) {
|
|
|
53
62
|
async function setupGatewayTestHome() {
|
|
54
63
|
previousHome = process.env.HOME;
|
|
55
64
|
previousUserProfile = process.env.USERPROFILE;
|
|
56
|
-
previousStateDir = process.env.POOLBOT_STATE_DIR
|
|
57
|
-
previousConfigPath = process.env.POOLBOT_CONFIG_PATH
|
|
58
|
-
previousSkipBrowserControl =
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
process.env.POOLBOT_BUNDLED_PLUGINS_DIR ?? process.env.CLAWDBOT_BUNDLED_PLUGINS_DIR;
|
|
65
|
+
previousStateDir = process.env.POOLBOT_STATE_DIR;
|
|
66
|
+
previousConfigPath = process.env.POOLBOT_CONFIG_PATH;
|
|
67
|
+
previousSkipBrowserControl = process.env.POOLBOT_SKIP_BROWSER_CONTROL_SERVER;
|
|
68
|
+
previousSkipGmailWatcher = process.env.POOLBOT_SKIP_GMAIL_WATCHER;
|
|
69
|
+
previousSkipCanvasHost = process.env.POOLBOT_SKIP_CANVAS_HOST;
|
|
70
|
+
previousBundledPluginsDir = process.env.POOLBOT_BUNDLED_PLUGINS_DIR;
|
|
71
|
+
previousSkipChannels = process.env.POOLBOT_SKIP_CHANNELS;
|
|
72
|
+
previousSkipProviders = process.env.POOLBOT_SKIP_PROVIDERS;
|
|
73
|
+
previousSkipCron = process.env.POOLBOT_SKIP_CRON;
|
|
74
|
+
previousMinimalGateway = process.env.POOLBOT_TEST_MINIMAL_GATEWAY;
|
|
67
75
|
tempHome = await fs.mkdtemp(path.join(os.tmpdir(), "poolbot-gateway-home-"));
|
|
68
76
|
process.env.HOME = tempHome;
|
|
69
77
|
process.env.USERPROFILE = tempHome;
|
|
70
78
|
process.env.POOLBOT_STATE_DIR = path.join(tempHome, ".poolbot");
|
|
71
|
-
process.env.CLAWDBOT_STATE_DIR = path.join(tempHome, ".poolbot");
|
|
72
79
|
delete process.env.POOLBOT_CONFIG_PATH;
|
|
73
|
-
delete process.env.CLAWDBOT_CONFIG_PATH;
|
|
74
80
|
}
|
|
75
81
|
function applyGatewaySkipEnv() {
|
|
76
82
|
process.env.POOLBOT_SKIP_BROWSER_CONTROL_SERVER = "1";
|
|
77
|
-
process.env.CLAWDBOT_SKIP_BROWSER_CONTROL_SERVER = "1";
|
|
78
83
|
process.env.POOLBOT_SKIP_GMAIL_WATCHER = "1";
|
|
79
|
-
process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = "1";
|
|
80
84
|
process.env.POOLBOT_SKIP_CANVAS_HOST = "1";
|
|
81
|
-
process.env.
|
|
82
|
-
|
|
85
|
+
process.env.POOLBOT_SKIP_CHANNELS = "1";
|
|
86
|
+
process.env.POOLBOT_SKIP_PROVIDERS = "1";
|
|
87
|
+
process.env.POOLBOT_SKIP_CRON = "1";
|
|
88
|
+
process.env.POOLBOT_TEST_MINIMAL_GATEWAY = "1";
|
|
89
|
+
process.env.POOLBOT_BUNDLED_PLUGINS_DIR = tempHome
|
|
83
90
|
? path.join(tempHome, "poolbot-test-no-bundled-extensions")
|
|
84
91
|
: "poolbot-test-no-bundled-extensions";
|
|
85
|
-
process.env.POOLBOT_BUNDLED_PLUGINS_DIR = bundledDir;
|
|
86
|
-
process.env.CLAWDBOT_BUNDLED_PLUGINS_DIR = bundledDir;
|
|
87
92
|
}
|
|
88
93
|
async function resetGatewayTestState(options) {
|
|
89
94
|
// Some tests intentionally use fake timers; ensure they don't leak into gateway suites.
|
|
@@ -93,12 +98,18 @@ async function resetGatewayTestState(options) {
|
|
|
93
98
|
throw new Error("resetGatewayTestState called before temp home was initialized");
|
|
94
99
|
}
|
|
95
100
|
applyGatewaySkipEnv();
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
101
|
+
if (options.uniqueConfigRoot) {
|
|
102
|
+
tempConfigRoot = await fs.mkdtemp(path.join(tempHome, "poolbot-test-"));
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
tempConfigRoot = path.join(tempHome, ".poolbot-test");
|
|
106
|
+
await fs.rm(tempConfigRoot, { recursive: true, force: true });
|
|
107
|
+
await fs.mkdir(tempConfigRoot, { recursive: true });
|
|
108
|
+
}
|
|
99
109
|
setTestConfigRoot(tempConfigRoot);
|
|
100
110
|
sessionStoreSaveDelayMs.value = 0;
|
|
101
111
|
testTailnetIPv4.value = undefined;
|
|
112
|
+
testTailscaleWhois.value = null;
|
|
102
113
|
testState.gatewayBind = undefined;
|
|
103
114
|
testState.gatewayAuth = { mode: "token", token: "test-gateway-token-1234567890" };
|
|
104
115
|
testState.gatewayControlUi = undefined;
|
|
@@ -126,7 +137,7 @@ async function resetGatewayTestState(options) {
|
|
|
126
137
|
embeddedRunMock.waitResults.clear();
|
|
127
138
|
drainSystemEvents(resolveMainSessionKeyFromConfig());
|
|
128
139
|
resetAgentRunContextForTest();
|
|
129
|
-
const mod = await
|
|
140
|
+
const mod = await getServerModule();
|
|
130
141
|
mod.__resetModelCatalogCacheForTest();
|
|
131
142
|
piSdkMock.enabled = false;
|
|
132
143
|
piSdkMock.discoverCalls = 0;
|
|
@@ -136,61 +147,77 @@ async function cleanupGatewayTestHome(options) {
|
|
|
136
147
|
vi.useRealTimers();
|
|
137
148
|
resetLogger();
|
|
138
149
|
if (options.restoreEnv) {
|
|
139
|
-
if (previousHome === undefined)
|
|
150
|
+
if (previousHome === undefined) {
|
|
140
151
|
delete process.env.HOME;
|
|
141
|
-
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
142
154
|
process.env.HOME = previousHome;
|
|
143
|
-
|
|
155
|
+
}
|
|
156
|
+
if (previousUserProfile === undefined) {
|
|
144
157
|
delete process.env.USERPROFILE;
|
|
145
|
-
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
146
160
|
process.env.USERPROFILE = previousUserProfile;
|
|
161
|
+
}
|
|
147
162
|
if (previousStateDir === undefined) {
|
|
148
163
|
delete process.env.POOLBOT_STATE_DIR;
|
|
149
|
-
delete process.env.CLAWDBOT_STATE_DIR;
|
|
150
164
|
}
|
|
151
165
|
else {
|
|
152
166
|
process.env.POOLBOT_STATE_DIR = previousStateDir;
|
|
153
|
-
process.env.CLAWDBOT_STATE_DIR = previousStateDir;
|
|
154
167
|
}
|
|
155
168
|
if (previousConfigPath === undefined) {
|
|
156
169
|
delete process.env.POOLBOT_CONFIG_PATH;
|
|
157
|
-
delete process.env.CLAWDBOT_CONFIG_PATH;
|
|
158
170
|
}
|
|
159
171
|
else {
|
|
160
172
|
process.env.POOLBOT_CONFIG_PATH = previousConfigPath;
|
|
161
|
-
process.env.CLAWDBOT_CONFIG_PATH = previousConfigPath;
|
|
162
173
|
}
|
|
163
174
|
if (previousSkipBrowserControl === undefined) {
|
|
164
175
|
delete process.env.POOLBOT_SKIP_BROWSER_CONTROL_SERVER;
|
|
165
|
-
delete process.env.CLAWDBOT_SKIP_BROWSER_CONTROL_SERVER;
|
|
166
176
|
}
|
|
167
177
|
else {
|
|
168
178
|
process.env.POOLBOT_SKIP_BROWSER_CONTROL_SERVER = previousSkipBrowserControl;
|
|
169
|
-
process.env.CLAWDBOT_SKIP_BROWSER_CONTROL_SERVER = previousSkipBrowserControl;
|
|
170
179
|
}
|
|
171
180
|
if (previousSkipGmailWatcher === undefined) {
|
|
172
181
|
delete process.env.POOLBOT_SKIP_GMAIL_WATCHER;
|
|
173
|
-
delete process.env.CLAWDBOT_SKIP_GMAIL_WATCHER;
|
|
174
182
|
}
|
|
175
183
|
else {
|
|
176
184
|
process.env.POOLBOT_SKIP_GMAIL_WATCHER = previousSkipGmailWatcher;
|
|
177
|
-
process.env.CLAWDBOT_SKIP_GMAIL_WATCHER = previousSkipGmailWatcher;
|
|
178
185
|
}
|
|
179
186
|
if (previousSkipCanvasHost === undefined) {
|
|
180
187
|
delete process.env.POOLBOT_SKIP_CANVAS_HOST;
|
|
181
|
-
delete process.env.CLAWDBOT_SKIP_CANVAS_HOST;
|
|
182
188
|
}
|
|
183
189
|
else {
|
|
184
190
|
process.env.POOLBOT_SKIP_CANVAS_HOST = previousSkipCanvasHost;
|
|
185
|
-
process.env.CLAWDBOT_SKIP_CANVAS_HOST = previousSkipCanvasHost;
|
|
186
191
|
}
|
|
187
192
|
if (previousBundledPluginsDir === undefined) {
|
|
188
193
|
delete process.env.POOLBOT_BUNDLED_PLUGINS_DIR;
|
|
189
|
-
delete process.env.CLAWDBOT_BUNDLED_PLUGINS_DIR;
|
|
190
194
|
}
|
|
191
195
|
else {
|
|
192
196
|
process.env.POOLBOT_BUNDLED_PLUGINS_DIR = previousBundledPluginsDir;
|
|
193
|
-
|
|
197
|
+
}
|
|
198
|
+
if (previousSkipChannels === undefined) {
|
|
199
|
+
delete process.env.POOLBOT_SKIP_CHANNELS;
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
process.env.POOLBOT_SKIP_CHANNELS = previousSkipChannels;
|
|
203
|
+
}
|
|
204
|
+
if (previousSkipProviders === undefined) {
|
|
205
|
+
delete process.env.POOLBOT_SKIP_PROVIDERS;
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
process.env.POOLBOT_SKIP_PROVIDERS = previousSkipProviders;
|
|
209
|
+
}
|
|
210
|
+
if (previousSkipCron === undefined) {
|
|
211
|
+
delete process.env.POOLBOT_SKIP_CRON;
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
process.env.POOLBOT_SKIP_CRON = previousSkipCron;
|
|
215
|
+
}
|
|
216
|
+
if (previousMinimalGateway === undefined) {
|
|
217
|
+
delete process.env.POOLBOT_TEST_MINIMAL_GATEWAY;
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
process.env.POOLBOT_TEST_MINIMAL_GATEWAY = previousMinimalGateway;
|
|
194
221
|
}
|
|
195
222
|
}
|
|
196
223
|
if (options.restoreEnv && tempHome) {
|
|
@@ -269,14 +296,46 @@ timeoutMs = 10_000) {
|
|
|
269
296
|
});
|
|
270
297
|
}
|
|
271
298
|
export async function startGatewayServer(port, opts) {
|
|
272
|
-
const mod = await
|
|
299
|
+
const mod = await getServerModule();
|
|
273
300
|
const resolvedOpts = opts?.controlUiEnabled === undefined ? { ...opts, controlUiEnabled: false } : opts;
|
|
274
301
|
return await mod.startGatewayServer(port, resolvedOpts);
|
|
275
302
|
}
|
|
303
|
+
async function startGatewayServerWithRetries(params) {
|
|
304
|
+
let port = params.port;
|
|
305
|
+
for (let attempt = 0; attempt < 10; attempt++) {
|
|
306
|
+
try {
|
|
307
|
+
return {
|
|
308
|
+
port,
|
|
309
|
+
server: await startGatewayServer(port, params.opts),
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
catch (err) {
|
|
313
|
+
const code = err.cause?.code;
|
|
314
|
+
if (code !== "EADDRINUSE") {
|
|
315
|
+
throw err;
|
|
316
|
+
}
|
|
317
|
+
port = await getFreePort();
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
throw new Error("failed to start gateway server after retries");
|
|
321
|
+
}
|
|
322
|
+
export async function withGatewayServer(fn, opts) {
|
|
323
|
+
const started = await startGatewayServerWithRetries({
|
|
324
|
+
port: opts?.port ?? (await getFreePort()),
|
|
325
|
+
opts: opts?.serverOptions,
|
|
326
|
+
});
|
|
327
|
+
try {
|
|
328
|
+
return await fn({ port: started.port, server: started.server });
|
|
329
|
+
}
|
|
330
|
+
finally {
|
|
331
|
+
await started.server.close();
|
|
332
|
+
}
|
|
333
|
+
}
|
|
276
334
|
export async function startServerWithClient(token, opts) {
|
|
335
|
+
const { wsHeaders, ...gatewayOpts } = opts ?? {};
|
|
277
336
|
let port = await getFreePort();
|
|
278
|
-
const envSnapshot = captureEnv(["POOLBOT_GATEWAY_TOKEN"
|
|
279
|
-
const prev = process.env.POOLBOT_GATEWAY_TOKEN
|
|
337
|
+
const envSnapshot = captureEnv(["POOLBOT_GATEWAY_TOKEN"]);
|
|
338
|
+
const prev = process.env.POOLBOT_GATEWAY_TOKEN;
|
|
280
339
|
if (typeof token === "string") {
|
|
281
340
|
testState.gatewayAuth = { mode: "token", token };
|
|
282
341
|
}
|
|
@@ -286,29 +345,14 @@ export async function startServerWithClient(token, opts) {
|
|
|
286
345
|
: undefined);
|
|
287
346
|
if (fallbackToken === undefined) {
|
|
288
347
|
delete process.env.POOLBOT_GATEWAY_TOKEN;
|
|
289
|
-
delete process.env.CLAWDBOT_GATEWAY_TOKEN;
|
|
290
348
|
}
|
|
291
349
|
else {
|
|
292
350
|
process.env.POOLBOT_GATEWAY_TOKEN = fallbackToken;
|
|
293
|
-
process.env.CLAWDBOT_GATEWAY_TOKEN = fallbackToken;
|
|
294
351
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
break;
|
|
300
|
-
}
|
|
301
|
-
catch (err) {
|
|
302
|
-
const code = err.cause?.code;
|
|
303
|
-
if (code !== "EADDRINUSE")
|
|
304
|
-
throw err;
|
|
305
|
-
port = await getFreePort();
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
if (!server) {
|
|
309
|
-
throw new Error("failed to start gateway server after retries");
|
|
310
|
-
}
|
|
311
|
-
const ws = new WebSocket(`ws://127.0.0.1:${port}`);
|
|
352
|
+
const started = await startGatewayServerWithRetries({ port, opts: gatewayOpts });
|
|
353
|
+
port = started.port;
|
|
354
|
+
const server = started.server;
|
|
355
|
+
const ws = new WebSocket(`ws://127.0.0.1:${port}`, wsHeaders ? { headers: wsHeaders } : undefined);
|
|
312
356
|
await new Promise((resolve, reject) => {
|
|
313
357
|
const timer = setTimeout(() => reject(new Error("timeout waiting for ws open")), 10_000);
|
|
314
358
|
const cleanup = () => {
|
|
@@ -349,20 +393,26 @@ export async function connectReq(ws, opts) {
|
|
|
349
393
|
? undefined
|
|
350
394
|
: typeof testState.gatewayAuth?.token === "string"
|
|
351
395
|
? (testState.gatewayAuth.token ?? undefined)
|
|
352
|
-
:
|
|
396
|
+
: process.env.POOLBOT_GATEWAY_TOKEN;
|
|
353
397
|
const defaultPassword = opts?.skipDefaultAuth === true
|
|
354
398
|
? undefined
|
|
355
399
|
: typeof testState.gatewayAuth?.password === "string"
|
|
356
400
|
? (testState.gatewayAuth.password ?? undefined)
|
|
357
|
-
:
|
|
401
|
+
: process.env.POOLBOT_GATEWAY_PASSWORD;
|
|
358
402
|
const token = opts?.token ?? defaultToken;
|
|
359
403
|
const password = opts?.password ?? defaultPassword;
|
|
360
|
-
const requestedScopes = Array.isArray(opts?.scopes)
|
|
404
|
+
const requestedScopes = Array.isArray(opts?.scopes)
|
|
405
|
+
? opts.scopes
|
|
406
|
+
: role === "operator"
|
|
407
|
+
? ["operator.admin"]
|
|
408
|
+
: [];
|
|
361
409
|
const device = (() => {
|
|
362
|
-
if (opts?.device === null)
|
|
410
|
+
if (opts?.device === null) {
|
|
363
411
|
return undefined;
|
|
364
|
-
|
|
412
|
+
}
|
|
413
|
+
if (opts?.device) {
|
|
365
414
|
return opts.device;
|
|
415
|
+
}
|
|
366
416
|
const identity = loadOrCreateDeviceIdentity();
|
|
367
417
|
const signedAtMs = Date.now();
|
|
368
418
|
const payload = buildDeviceAuthPayload({
|
|
@@ -394,7 +444,7 @@ export async function connectReq(ws, opts) {
|
|
|
394
444
|
commands: opts?.commands ?? [],
|
|
395
445
|
permissions: opts?.permissions ?? undefined,
|
|
396
446
|
role,
|
|
397
|
-
scopes:
|
|
447
|
+
scopes: requestedScopes,
|
|
398
448
|
auth: token || password
|
|
399
449
|
? {
|
|
400
450
|
token,
|
|
@@ -405,8 +455,9 @@ export async function connectReq(ws, opts) {
|
|
|
405
455
|
},
|
|
406
456
|
}));
|
|
407
457
|
const isResponseForId = (o) => {
|
|
408
|
-
if (!o || typeof o !== "object" || Array.isArray(o))
|
|
458
|
+
if (!o || typeof o !== "object" || Array.isArray(o)) {
|
|
409
459
|
return false;
|
|
460
|
+
}
|
|
410
461
|
const rec = o;
|
|
411
462
|
return rec.type === "res" && rec.id === id;
|
|
412
463
|
};
|
|
@@ -418,13 +469,45 @@ export async function connectOk(ws, opts) {
|
|
|
418
469
|
expect(res.payload?.type).toBe("hello-ok");
|
|
419
470
|
return res.payload;
|
|
420
471
|
}
|
|
472
|
+
export async function connectWebchatClient(params) {
|
|
473
|
+
const origin = params.origin ?? `http://127.0.0.1:${params.port}`;
|
|
474
|
+
const ws = new WebSocket(`ws://127.0.0.1:${params.port}`, {
|
|
475
|
+
headers: { origin },
|
|
476
|
+
});
|
|
477
|
+
await new Promise((resolve, reject) => {
|
|
478
|
+
const timer = setTimeout(() => reject(new Error("timeout waiting for ws open")), 10_000);
|
|
479
|
+
const onOpen = () => {
|
|
480
|
+
clearTimeout(timer);
|
|
481
|
+
ws.off("error", onError);
|
|
482
|
+
resolve();
|
|
483
|
+
};
|
|
484
|
+
const onError = (err) => {
|
|
485
|
+
clearTimeout(timer);
|
|
486
|
+
ws.off("open", onOpen);
|
|
487
|
+
reject(err);
|
|
488
|
+
};
|
|
489
|
+
ws.once("open", onOpen);
|
|
490
|
+
ws.once("error", onError);
|
|
491
|
+
});
|
|
492
|
+
await connectOk(ws, {
|
|
493
|
+
client: params.client ??
|
|
494
|
+
{
|
|
495
|
+
id: GATEWAY_CLIENT_NAMES.WEBCHAT,
|
|
496
|
+
version: "1.0.0",
|
|
497
|
+
platform: "test",
|
|
498
|
+
mode: GATEWAY_CLIENT_MODES.WEBCHAT,
|
|
499
|
+
},
|
|
500
|
+
});
|
|
501
|
+
return ws;
|
|
502
|
+
}
|
|
421
503
|
export async function rpcReq(ws, method, params, timeoutMs) {
|
|
422
504
|
const { randomUUID } = await import("node:crypto");
|
|
423
505
|
const id = randomUUID();
|
|
424
506
|
ws.send(JSON.stringify({ type: "req", id, method, params }));
|
|
425
507
|
return await onceMessage(ws, (o) => {
|
|
426
|
-
if (!o || typeof o !== "object" || Array.isArray(o))
|
|
508
|
+
if (!o || typeof o !== "object" || Array.isArray(o)) {
|
|
427
509
|
return false;
|
|
510
|
+
}
|
|
428
511
|
const rec = o;
|
|
429
512
|
return rec.type === "res" && rec.id === id;
|
|
430
513
|
}, timeoutMs);
|
|
@@ -434,8 +517,9 @@ export async function waitForSystemEvent(timeoutMs = 2000) {
|
|
|
434
517
|
const deadline = Date.now() + timeoutMs;
|
|
435
518
|
while (Date.now() < deadline) {
|
|
436
519
|
const events = peekSystemEvents(sessionKey);
|
|
437
|
-
if (events.length > 0)
|
|
520
|
+
if (events.length > 0) {
|
|
438
521
|
return events;
|
|
522
|
+
}
|
|
439
523
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
440
524
|
}
|
|
441
525
|
throw new Error("timeout waiting for system event");
|