@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.
Files changed (235) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/acp/client.js +207 -18
  3. package/dist/acp/secret-file.js +22 -0
  4. package/dist/agents/agent-scope.js +10 -0
  5. package/dist/agents/bash-process-registry.test-helpers.js +29 -0
  6. package/dist/agents/bash-tools.exec-approval-request.js +20 -0
  7. package/dist/agents/bash-tools.exec-host-gateway.js +230 -0
  8. package/dist/agents/bash-tools.exec-host-node.js +235 -0
  9. package/dist/agents/bash-tools.exec-types.js +1 -0
  10. package/dist/agents/bash-tools.process.js +224 -218
  11. package/dist/agents/content-blocks.js +16 -0
  12. package/dist/agents/model-fallback.js +96 -101
  13. package/dist/agents/models-config.providers.js +299 -182
  14. package/dist/agents/pi-embedded-payloads.js +1 -0
  15. package/dist/agents/pi-embedded-runner/run.overflow-compaction.fixture.js +34 -0
  16. package/dist/agents/skills.test-helpers.js +13 -0
  17. package/dist/agents/stable-stringify.js +12 -0
  18. package/dist/agents/subagent-registry.mocks.shared.js +12 -0
  19. package/dist/agents/test-helpers/assistant-message-fixtures.js +29 -0
  20. package/dist/agents/test-helpers/pi-tools-sandbox-context.js +27 -0
  21. package/dist/agents/tool-policy-shared.js +108 -0
  22. package/dist/agents/tools/browser-tool.js +160 -54
  23. package/dist/agents/tools/cron-tool.test-helpers.js +12 -0
  24. package/dist/agents/tools/discord-actions-moderation-shared.js +27 -0
  25. package/dist/agents/tools/image-tool.js +214 -99
  26. package/dist/agents/tools/sessions-history-tool.js +140 -108
  27. package/dist/agents/workspace.js +222 -46
  28. package/dist/auto-reply/commands-registry.js +15 -18
  29. package/dist/auto-reply/fallback-state.js +114 -0
  30. package/dist/auto-reply/model-runtime.js +68 -0
  31. package/dist/auto-reply/reply/agent-runner-execution.js +36 -4
  32. package/dist/auto-reply/reply/agent-runner.js +165 -39
  33. package/dist/auto-reply/reply/commands-setunset-standard.js +13 -0
  34. package/dist/browser/config.js +26 -0
  35. package/dist/browser/navigation-guard.js +31 -0
  36. package/dist/browser/routes/agent.act.js +431 -424
  37. package/dist/browser/routes/agent.shared.js +47 -3
  38. package/dist/browser/routes/agent.snapshot.js +122 -116
  39. package/dist/browser/routes/agent.storage.js +303 -297
  40. package/dist/browser/routes/tabs.js +154 -100
  41. package/dist/browser/server-lifecycle.js +37 -0
  42. package/dist/build-info.json +3 -3
  43. package/dist/channels/allow-from.js +25 -0
  44. package/dist/channels/plugins/account-action-gate.js +13 -0
  45. package/dist/channels/plugins/message-actions.js +10 -0
  46. package/dist/channels/telegram/api.js +18 -0
  47. package/dist/cli/argv.js +84 -21
  48. package/dist/cli/banner.js +2 -1
  49. package/dist/cli/exec-approvals-cli.js +92 -124
  50. package/dist/cli/memory-cli.js +158 -61
  51. package/dist/cli/nodes-cli/register.push.js +63 -0
  52. package/dist/cli/nodes-media-utils.js +21 -0
  53. package/dist/cli/plugins-cli.js +245 -61
  54. package/dist/cli/program/build-program.js +3 -1
  55. package/dist/cli/program/command-registry.js +223 -136
  56. package/dist/cli/program/help.js +43 -12
  57. package/dist/cli/route.js +1 -1
  58. package/dist/cli/test-runtime-capture.js +24 -0
  59. package/dist/commands/agent.js +163 -87
  60. package/dist/commands/channels.mock-harness.js +23 -0
  61. package/dist/commands/daemon-install-runtime-warning.js +11 -0
  62. package/dist/commands/onboard-helpers.js +4 -4
  63. package/dist/commands/sessions.test-helpers.js +61 -0
  64. package/dist/compat/legacy-names.js +2 -2
  65. package/dist/config/commands.js +3 -0
  66. package/dist/config/config.js +1 -1
  67. package/dist/config/env-substitution.js +62 -34
  68. package/dist/config/env-vars.js +9 -0
  69. package/dist/config/io.js +571 -171
  70. package/dist/config/merge-patch.js +50 -4
  71. package/dist/config/redact-snapshot.js +404 -76
  72. package/dist/config/schema.js +58 -570
  73. package/dist/config/validation.js +140 -85
  74. package/dist/config/zod-schema.hooks.js +40 -11
  75. package/dist/config/zod-schema.installs.js +20 -0
  76. package/dist/config/zod-schema.js +8 -7
  77. package/dist/control-ui/assets/{index-HRr1grwl.js → index-Dvkl4Xlx.js} +2 -1
  78. package/dist/control-ui/assets/{index-HRr1grwl.js.map → index-Dvkl4Xlx.js.map} +1 -1
  79. package/dist/control-ui/index.html +1 -1
  80. package/dist/daemon/cmd-argv.js +21 -0
  81. package/dist/daemon/cmd-set.js +58 -0
  82. package/dist/daemon/service-types.js +1 -0
  83. package/dist/discord/monitor/exec-approvals.js +357 -162
  84. package/dist/gateway/auth.js +38 -3
  85. package/dist/gateway/call.js +149 -68
  86. package/dist/gateway/canvas-capability.js +75 -0
  87. package/dist/gateway/control-plane-audit.js +28 -0
  88. package/dist/gateway/control-plane-rate-limit.js +53 -0
  89. package/dist/gateway/events.js +1 -0
  90. package/dist/gateway/hooks.js +109 -54
  91. package/dist/gateway/http-common.js +22 -0
  92. package/dist/gateway/method-scopes.js +169 -0
  93. package/dist/gateway/net.js +23 -0
  94. package/dist/gateway/openresponses-http.js +120 -110
  95. package/dist/gateway/probe-auth.js +2 -0
  96. package/dist/gateway/protocol/index.js +3 -2
  97. package/dist/gateway/protocol/schema/protocol-schemas.js +2 -0
  98. package/dist/gateway/protocol/schema/push.js +18 -0
  99. package/dist/gateway/protocol/schema.js +1 -0
  100. package/dist/gateway/server-http.js +236 -52
  101. package/dist/gateway/server-methods/agent.js +162 -24
  102. package/dist/gateway/server-methods/chat.js +461 -130
  103. package/dist/gateway/server-methods/config.js +193 -150
  104. package/dist/gateway/server-methods/nodes.helpers.js +12 -0
  105. package/dist/gateway/server-methods/nodes.js +251 -69
  106. package/dist/gateway/server-methods/push.js +53 -0
  107. package/dist/gateway/server-reload-handlers.js +2 -3
  108. package/dist/gateway/server-runtime-config.js +5 -0
  109. package/dist/gateway/server-runtime-state.js +2 -0
  110. package/dist/gateway/server-ws-runtime.js +1 -0
  111. package/dist/gateway/server.impl.js +296 -139
  112. package/dist/gateway/session-preview.test-helpers.js +11 -0
  113. package/dist/gateway/startup-auth.js +126 -0
  114. package/dist/gateway/test-helpers.agent-results.js +15 -0
  115. package/dist/gateway/test-helpers.mocks.js +37 -14
  116. package/dist/gateway/test-helpers.server.js +161 -77
  117. package/dist/hooks/bundled/session-memory/handler.js +165 -34
  118. package/dist/hooks/gmail-watcher-lifecycle.js +23 -0
  119. package/dist/infra/archive-path.js +49 -0
  120. package/dist/infra/device-pairing.js +148 -167
  121. package/dist/infra/exec-approvals-allowlist.js +19 -70
  122. package/dist/infra/exec-approvals-analysis.js +44 -17
  123. package/dist/infra/exec-safe-bin-policy.js +269 -0
  124. package/dist/infra/fixed-window-rate-limit.js +33 -0
  125. package/dist/infra/git-root.js +61 -0
  126. package/dist/infra/heartbeat-active-hours.js +2 -2
  127. package/dist/infra/heartbeat-reason.js +40 -0
  128. package/dist/infra/heartbeat-runner.js +72 -32
  129. package/dist/infra/install-source-utils.js +91 -7
  130. package/dist/infra/node-pairing.js +50 -105
  131. package/dist/infra/npm-integrity.js +45 -0
  132. package/dist/infra/npm-pack-install.js +40 -0
  133. package/dist/infra/outbound/channel-adapters.js +20 -7
  134. package/dist/infra/outbound/message-action-runner.js +107 -327
  135. package/dist/infra/outbound/message.js +59 -36
  136. package/dist/infra/outbound/outbound-policy.js +52 -25
  137. package/dist/infra/outbound/outbound-send-service.js +58 -71
  138. package/dist/infra/pairing-files.js +10 -0
  139. package/dist/infra/plain-object.js +9 -0
  140. package/dist/infra/push-apns.js +365 -0
  141. package/dist/infra/restart-sentinel.js +16 -1
  142. package/dist/infra/restart.js +229 -26
  143. package/dist/infra/scp-host.js +54 -0
  144. package/dist/infra/update-startup.js +86 -9
  145. package/dist/media/inbound-path-policy.js +114 -0
  146. package/dist/media/input-files.js +16 -0
  147. package/dist/memory/test-manager.js +8 -0
  148. package/dist/plugin-sdk/temp-path.js +47 -0
  149. package/dist/plugins/discovery.js +217 -23
  150. package/dist/plugins/hook-runner-global.js +16 -0
  151. package/dist/plugins/loader.js +192 -26
  152. package/dist/plugins/logger.js +8 -0
  153. package/dist/plugins/manifest-registry.js +3 -0
  154. package/dist/plugins/path-safety.js +34 -0
  155. package/dist/plugins/registry.js +5 -2
  156. package/dist/plugins/runtime/index.js +271 -206
  157. package/dist/providers/github-copilot-models.js +4 -1
  158. package/dist/security/audit-channel.js +8 -19
  159. package/dist/security/audit-extra.async.js +354 -182
  160. package/dist/security/audit-extra.js +11 -1
  161. package/dist/security/audit-extra.sync.js +340 -33
  162. package/dist/security/audit-fs.js +31 -13
  163. package/dist/security/audit.js +145 -371
  164. package/dist/security/dm-policy-shared.js +24 -0
  165. package/dist/security/external-content.js +20 -8
  166. package/dist/security/fix.js +49 -85
  167. package/dist/security/scan-paths.js +20 -0
  168. package/dist/security/secret-equal.js +3 -7
  169. package/dist/security/windows-acl.js +30 -15
  170. package/dist/shared/node-list-parse.js +13 -0
  171. package/dist/shared/operator-scope-compat.js +37 -0
  172. package/dist/shared/text-chunking.js +29 -0
  173. package/dist/slack/blocks.test-helpers.js +31 -0
  174. package/dist/slack/monitor/mrkdwn.js +8 -0
  175. package/dist/telegram/bot-message-dispatch.js +366 -164
  176. package/dist/telegram/draft-stream.js +30 -7
  177. package/dist/telegram/reasoning-lane-coordinator.js +128 -0
  178. package/dist/terminal/prompt-select-styled.js +9 -0
  179. package/dist/test-utils/command-runner.js +6 -0
  180. package/dist/test-utils/internal-hook-event-payload.js +10 -0
  181. package/dist/test-utils/model-auth-mock.js +12 -0
  182. package/dist/test-utils/provider-usage-fetch.js +14 -0
  183. package/dist/test-utils/temp-home.js +33 -0
  184. package/dist/tui/components/chat-log.js +9 -0
  185. package/dist/tui/tui-command-handlers.js +36 -27
  186. package/dist/tui/tui-event-handlers.js +122 -32
  187. package/dist/tui/tui.js +181 -45
  188. package/dist/utils/mask-api-key.js +10 -0
  189. package/dist/utils/run-with-concurrency.js +39 -0
  190. package/dist/web/media.js +4 -0
  191. package/docs/tools/slash-commands.md +5 -1
  192. package/extensions/bluebubbles/package.json +1 -1
  193. package/extensions/copilot-proxy/package.json +1 -1
  194. package/extensions/diagnostics-otel/package.json +1 -1
  195. package/extensions/discord/package.json +1 -1
  196. package/extensions/feishu/package.json +1 -1
  197. package/extensions/feishu/src/external-keys.ts +19 -0
  198. package/extensions/google-antigravity-auth/package.json +1 -1
  199. package/extensions/google-gemini-cli-auth/package.json +1 -1
  200. package/extensions/googlechat/package.json +1 -1
  201. package/extensions/imessage/package.json +1 -1
  202. package/extensions/irc/package.json +1 -1
  203. package/extensions/line/package.json +1 -1
  204. package/extensions/llm-task/package.json +1 -1
  205. package/extensions/lobster/package.json +1 -1
  206. package/extensions/lobster/src/windows-spawn.ts +193 -0
  207. package/extensions/matrix/CHANGELOG.md +5 -0
  208. package/extensions/matrix/package.json +1 -1
  209. package/extensions/matrix/src/matrix/actions/limits.ts +6 -0
  210. package/extensions/mattermost/package.json +1 -1
  211. package/extensions/mattermost/src/mattermost/reactions.test-helpers.ts +83 -0
  212. package/extensions/memory-core/package.json +1 -1
  213. package/extensions/memory-lancedb/package.json +1 -1
  214. package/extensions/minimax-portal-auth/package.json +1 -1
  215. package/extensions/msteams/CHANGELOG.md +5 -0
  216. package/extensions/msteams/package.json +1 -1
  217. package/extensions/nextcloud-talk/package.json +1 -1
  218. package/extensions/nostr/CHANGELOG.md +5 -0
  219. package/extensions/nostr/package.json +1 -1
  220. package/extensions/open-prose/package.json +1 -1
  221. package/extensions/openai-codex-auth/package.json +1 -1
  222. package/extensions/signal/package.json +1 -1
  223. package/extensions/slack/package.json +1 -1
  224. package/extensions/telegram/package.json +1 -1
  225. package/extensions/tlon/package.json +1 -1
  226. package/extensions/twitch/CHANGELOG.md +5 -0
  227. package/extensions/twitch/package.json +1 -1
  228. package/extensions/voice-call/CHANGELOG.md +5 -0
  229. package/extensions/voice-call/package.json +1 -1
  230. package/extensions/whatsapp/package.json +1 -1
  231. package/extensions/zalo/CHANGELOG.md +5 -0
  232. package/extensions/zalo/package.json +1 -1
  233. package/extensions/zalouser/CHANGELOG.md +5 -0
  234. package/extensions/zalouser/package.json +1 -1
  235. package/package.json +1 -1
@@ -1,386 +1,392 @@
1
- import { handleRouteError, readBody, requirePwAi, resolveProfileContext } from "./agent.shared.js";
1
+ import { readBody, resolveTargetIdFromBody, resolveTargetIdFromQuery, withPlaywrightRouteContext, } from "./agent.shared.js";
2
2
  import { jsonError, toBoolean, toNumber, toStringOrEmpty } from "./utils.js";
3
+ export function parseStorageKind(raw) {
4
+ if (raw === "local" || raw === "session") {
5
+ return raw;
6
+ }
7
+ return null;
8
+ }
9
+ export function parseStorageMutationRequest(kindParam, body) {
10
+ return {
11
+ kind: parseStorageKind(toStringOrEmpty(kindParam)),
12
+ targetId: resolveTargetIdFromBody(body),
13
+ };
14
+ }
15
+ export function parseRequiredStorageMutationRequest(kindParam, body) {
16
+ const parsed = parseStorageMutationRequest(kindParam, body);
17
+ if (!parsed.kind) {
18
+ return null;
19
+ }
20
+ return {
21
+ kind: parsed.kind,
22
+ targetId: parsed.targetId,
23
+ };
24
+ }
25
+ function parseStorageMutationOrRespond(res, kindParam, body) {
26
+ const parsed = parseRequiredStorageMutationRequest(kindParam, body);
27
+ if (!parsed) {
28
+ jsonError(res, 400, "kind must be local|session");
29
+ return null;
30
+ }
31
+ return parsed;
32
+ }
33
+ function parseStorageMutationFromRequest(req, res) {
34
+ const body = readBody(req);
35
+ const parsed = parseStorageMutationOrRespond(res, req.params.kind, body);
36
+ if (!parsed) {
37
+ return null;
38
+ }
39
+ return { body, parsed };
40
+ }
3
41
  export function registerBrowserAgentStorageRoutes(app, ctx) {
4
42
  app.get("/cookies", async (req, res) => {
5
- const profileCtx = resolveProfileContext(req, res, ctx);
6
- if (!profileCtx)
7
- return;
8
- const targetId = typeof req.query.targetId === "string" ? req.query.targetId.trim() : "";
9
- try {
10
- const tab = await profileCtx.ensureTabAvailable(targetId || undefined);
11
- const pw = await requirePwAi(res, "cookies");
12
- if (!pw)
13
- return;
14
- const result = await pw.cookiesGetViaPlaywright({
15
- cdpUrl: profileCtx.profile.cdpUrl,
16
- targetId: tab.targetId,
17
- });
18
- res.json({ ok: true, targetId: tab.targetId, ...result });
19
- }
20
- catch (err) {
21
- handleRouteError(ctx, res, err);
22
- }
43
+ const targetId = resolveTargetIdFromQuery(req.query);
44
+ await withPlaywrightRouteContext({
45
+ req,
46
+ res,
47
+ ctx,
48
+ targetId,
49
+ feature: "cookies",
50
+ run: async ({ cdpUrl, tab, pw }) => {
51
+ const result = await pw.cookiesGetViaPlaywright({
52
+ cdpUrl,
53
+ targetId: tab.targetId,
54
+ });
55
+ res.json({ ok: true, targetId: tab.targetId, ...result });
56
+ },
57
+ });
23
58
  });
24
59
  app.post("/cookies/set", async (req, res) => {
25
- const profileCtx = resolveProfileContext(req, res, ctx);
26
- if (!profileCtx)
27
- return;
28
60
  const body = readBody(req);
29
- const targetId = toStringOrEmpty(body.targetId) || undefined;
61
+ const targetId = resolveTargetIdFromBody(body);
30
62
  const cookie = body.cookie && typeof body.cookie === "object" && !Array.isArray(body.cookie)
31
63
  ? body.cookie
32
64
  : null;
33
- if (!cookie)
65
+ if (!cookie) {
34
66
  return jsonError(res, 400, "cookie is required");
35
- try {
36
- const tab = await profileCtx.ensureTabAvailable(targetId);
37
- const pw = await requirePwAi(res, "cookies set");
38
- if (!pw)
39
- return;
40
- await pw.cookiesSetViaPlaywright({
41
- cdpUrl: profileCtx.profile.cdpUrl,
42
- targetId: tab.targetId,
43
- cookie: {
44
- name: toStringOrEmpty(cookie.name),
45
- value: toStringOrEmpty(cookie.value),
46
- url: toStringOrEmpty(cookie.url) || undefined,
47
- domain: toStringOrEmpty(cookie.domain) || undefined,
48
- path: toStringOrEmpty(cookie.path) || undefined,
49
- expires: toNumber(cookie.expires) ?? undefined,
50
- httpOnly: toBoolean(cookie.httpOnly) ?? undefined,
51
- secure: toBoolean(cookie.secure) ?? undefined,
52
- sameSite: cookie.sameSite === "Lax" || cookie.sameSite === "None" || cookie.sameSite === "Strict"
53
- ? cookie.sameSite
54
- : undefined,
55
- },
56
- });
57
- res.json({ ok: true, targetId: tab.targetId });
58
- }
59
- catch (err) {
60
- handleRouteError(ctx, res, err);
61
67
  }
68
+ await withPlaywrightRouteContext({
69
+ req,
70
+ res,
71
+ ctx,
72
+ targetId,
73
+ feature: "cookies set",
74
+ run: async ({ cdpUrl, tab, pw }) => {
75
+ await pw.cookiesSetViaPlaywright({
76
+ cdpUrl,
77
+ targetId: tab.targetId,
78
+ cookie: {
79
+ name: toStringOrEmpty(cookie.name),
80
+ value: toStringOrEmpty(cookie.value),
81
+ url: toStringOrEmpty(cookie.url) || undefined,
82
+ domain: toStringOrEmpty(cookie.domain) || undefined,
83
+ path: toStringOrEmpty(cookie.path) || undefined,
84
+ expires: toNumber(cookie.expires) ?? undefined,
85
+ httpOnly: toBoolean(cookie.httpOnly) ?? undefined,
86
+ secure: toBoolean(cookie.secure) ?? undefined,
87
+ sameSite: cookie.sameSite === "Lax" ||
88
+ cookie.sameSite === "None" ||
89
+ cookie.sameSite === "Strict"
90
+ ? cookie.sameSite
91
+ : undefined,
92
+ },
93
+ });
94
+ res.json({ ok: true, targetId: tab.targetId });
95
+ },
96
+ });
62
97
  });
63
98
  app.post("/cookies/clear", async (req, res) => {
64
- const profileCtx = resolveProfileContext(req, res, ctx);
65
- if (!profileCtx)
66
- return;
67
99
  const body = readBody(req);
68
- const targetId = toStringOrEmpty(body.targetId) || undefined;
69
- try {
70
- const tab = await profileCtx.ensureTabAvailable(targetId);
71
- const pw = await requirePwAi(res, "cookies clear");
72
- if (!pw)
73
- return;
74
- await pw.cookiesClearViaPlaywright({
75
- cdpUrl: profileCtx.profile.cdpUrl,
76
- targetId: tab.targetId,
77
- });
78
- res.json({ ok: true, targetId: tab.targetId });
79
- }
80
- catch (err) {
81
- handleRouteError(ctx, res, err);
82
- }
100
+ const targetId = resolveTargetIdFromBody(body);
101
+ await withPlaywrightRouteContext({
102
+ req,
103
+ res,
104
+ ctx,
105
+ targetId,
106
+ feature: "cookies clear",
107
+ run: async ({ cdpUrl, tab, pw }) => {
108
+ await pw.cookiesClearViaPlaywright({
109
+ cdpUrl,
110
+ targetId: tab.targetId,
111
+ });
112
+ res.json({ ok: true, targetId: tab.targetId });
113
+ },
114
+ });
83
115
  });
84
116
  app.get("/storage/:kind", async (req, res) => {
85
- const profileCtx = resolveProfileContext(req, res, ctx);
86
- if (!profileCtx)
87
- return;
88
- const kind = toStringOrEmpty(req.params.kind);
89
- if (kind !== "local" && kind !== "session")
117
+ const kind = parseStorageKind(toStringOrEmpty(req.params.kind));
118
+ if (!kind) {
90
119
  return jsonError(res, 400, "kind must be local|session");
91
- const targetId = typeof req.query.targetId === "string" ? req.query.targetId.trim() : "";
92
- const key = typeof req.query.key === "string" ? req.query.key : "";
93
- try {
94
- const tab = await profileCtx.ensureTabAvailable(targetId || undefined);
95
- const pw = await requirePwAi(res, "storage get");
96
- if (!pw)
97
- return;
98
- const result = await pw.storageGetViaPlaywright({
99
- cdpUrl: profileCtx.profile.cdpUrl,
100
- targetId: tab.targetId,
101
- kind,
102
- key: key.trim() || undefined,
103
- });
104
- res.json({ ok: true, targetId: tab.targetId, ...result });
105
- }
106
- catch (err) {
107
- handleRouteError(ctx, res, err);
108
120
  }
121
+ const targetId = resolveTargetIdFromQuery(req.query);
122
+ const key = toStringOrEmpty(req.query.key);
123
+ await withPlaywrightRouteContext({
124
+ req,
125
+ res,
126
+ ctx,
127
+ targetId,
128
+ feature: "storage get",
129
+ run: async ({ cdpUrl, tab, pw }) => {
130
+ const result = await pw.storageGetViaPlaywright({
131
+ cdpUrl,
132
+ targetId: tab.targetId,
133
+ kind,
134
+ key: key.trim() || undefined,
135
+ });
136
+ res.json({ ok: true, targetId: tab.targetId, ...result });
137
+ },
138
+ });
109
139
  });
110
140
  app.post("/storage/:kind/set", async (req, res) => {
111
- const profileCtx = resolveProfileContext(req, res, ctx);
112
- if (!profileCtx)
141
+ const mutation = parseStorageMutationFromRequest(req, res);
142
+ if (!mutation) {
113
143
  return;
114
- const kind = toStringOrEmpty(req.params.kind);
115
- if (kind !== "local" && kind !== "session")
116
- return jsonError(res, 400, "kind must be local|session");
117
- const body = readBody(req);
118
- const targetId = toStringOrEmpty(body.targetId) || undefined;
119
- const key = toStringOrEmpty(body.key);
120
- if (!key)
121
- return jsonError(res, 400, "key is required");
122
- const value = typeof body.value === "string" ? body.value : "";
123
- try {
124
- const tab = await profileCtx.ensureTabAvailable(targetId);
125
- const pw = await requirePwAi(res, "storage set");
126
- if (!pw)
127
- return;
128
- await pw.storageSetViaPlaywright({
129
- cdpUrl: profileCtx.profile.cdpUrl,
130
- targetId: tab.targetId,
131
- kind,
132
- key,
133
- value,
134
- });
135
- res.json({ ok: true, targetId: tab.targetId });
136
144
  }
137
- catch (err) {
138
- handleRouteError(ctx, res, err);
145
+ const key = toStringOrEmpty(mutation.body.key);
146
+ if (!key) {
147
+ return jsonError(res, 400, "key is required");
139
148
  }
149
+ const value = typeof mutation.body.value === "string" ? mutation.body.value : "";
150
+ await withPlaywrightRouteContext({
151
+ req,
152
+ res,
153
+ ctx,
154
+ targetId: mutation.parsed.targetId,
155
+ feature: "storage set",
156
+ run: async ({ cdpUrl, tab, pw }) => {
157
+ await pw.storageSetViaPlaywright({
158
+ cdpUrl,
159
+ targetId: tab.targetId,
160
+ kind: mutation.parsed.kind,
161
+ key,
162
+ value,
163
+ });
164
+ res.json({ ok: true, targetId: tab.targetId });
165
+ },
166
+ });
140
167
  });
141
168
  app.post("/storage/:kind/clear", async (req, res) => {
142
- const profileCtx = resolveProfileContext(req, res, ctx);
143
- if (!profileCtx)
169
+ const mutation = parseStorageMutationFromRequest(req, res);
170
+ if (!mutation) {
144
171
  return;
145
- const kind = toStringOrEmpty(req.params.kind);
146
- if (kind !== "local" && kind !== "session")
147
- return jsonError(res, 400, "kind must be local|session");
148
- const body = readBody(req);
149
- const targetId = toStringOrEmpty(body.targetId) || undefined;
150
- try {
151
- const tab = await profileCtx.ensureTabAvailable(targetId);
152
- const pw = await requirePwAi(res, "storage clear");
153
- if (!pw)
154
- return;
155
- await pw.storageClearViaPlaywright({
156
- cdpUrl: profileCtx.profile.cdpUrl,
157
- targetId: tab.targetId,
158
- kind,
159
- });
160
- res.json({ ok: true, targetId: tab.targetId });
161
- }
162
- catch (err) {
163
- handleRouteError(ctx, res, err);
164
172
  }
173
+ await withPlaywrightRouteContext({
174
+ req,
175
+ res,
176
+ ctx,
177
+ targetId: mutation.parsed.targetId,
178
+ feature: "storage clear",
179
+ run: async ({ cdpUrl, tab, pw }) => {
180
+ await pw.storageClearViaPlaywright({
181
+ cdpUrl,
182
+ targetId: tab.targetId,
183
+ kind: mutation.parsed.kind,
184
+ });
185
+ res.json({ ok: true, targetId: tab.targetId });
186
+ },
187
+ });
165
188
  });
166
189
  app.post("/set/offline", async (req, res) => {
167
- const profileCtx = resolveProfileContext(req, res, ctx);
168
- if (!profileCtx)
169
- return;
170
190
  const body = readBody(req);
171
- const targetId = toStringOrEmpty(body.targetId) || undefined;
191
+ const targetId = resolveTargetIdFromBody(body);
172
192
  const offline = toBoolean(body.offline);
173
- if (offline === undefined)
193
+ if (offline === undefined) {
174
194
  return jsonError(res, 400, "offline is required");
175
- try {
176
- const tab = await profileCtx.ensureTabAvailable(targetId);
177
- const pw = await requirePwAi(res, "offline");
178
- if (!pw)
179
- return;
180
- await pw.setOfflineViaPlaywright({
181
- cdpUrl: profileCtx.profile.cdpUrl,
182
- targetId: tab.targetId,
183
- offline,
184
- });
185
- res.json({ ok: true, targetId: tab.targetId });
186
- }
187
- catch (err) {
188
- handleRouteError(ctx, res, err);
189
195
  }
196
+ await withPlaywrightRouteContext({
197
+ req,
198
+ res,
199
+ ctx,
200
+ targetId,
201
+ feature: "offline",
202
+ run: async ({ cdpUrl, tab, pw }) => {
203
+ await pw.setOfflineViaPlaywright({
204
+ cdpUrl,
205
+ targetId: tab.targetId,
206
+ offline,
207
+ });
208
+ res.json({ ok: true, targetId: tab.targetId });
209
+ },
210
+ });
190
211
  });
191
212
  app.post("/set/headers", async (req, res) => {
192
- const profileCtx = resolveProfileContext(req, res, ctx);
193
- if (!profileCtx)
194
- return;
195
213
  const body = readBody(req);
196
- const targetId = toStringOrEmpty(body.targetId) || undefined;
214
+ const targetId = resolveTargetIdFromBody(body);
197
215
  const headers = body.headers && typeof body.headers === "object" && !Array.isArray(body.headers)
198
216
  ? body.headers
199
217
  : null;
200
- if (!headers)
218
+ if (!headers) {
201
219
  return jsonError(res, 400, "headers is required");
220
+ }
202
221
  const parsed = {};
203
222
  for (const [k, v] of Object.entries(headers)) {
204
- if (typeof v === "string")
223
+ if (typeof v === "string") {
205
224
  parsed[k] = v;
225
+ }
206
226
  }
207
- try {
208
- const tab = await profileCtx.ensureTabAvailable(targetId);
209
- const pw = await requirePwAi(res, "headers");
210
- if (!pw)
211
- return;
212
- await pw.setExtraHTTPHeadersViaPlaywright({
213
- cdpUrl: profileCtx.profile.cdpUrl,
214
- targetId: tab.targetId,
215
- headers: parsed,
216
- });
217
- res.json({ ok: true, targetId: tab.targetId });
218
- }
219
- catch (err) {
220
- handleRouteError(ctx, res, err);
221
- }
227
+ await withPlaywrightRouteContext({
228
+ req,
229
+ res,
230
+ ctx,
231
+ targetId,
232
+ feature: "headers",
233
+ run: async ({ cdpUrl, tab, pw }) => {
234
+ await pw.setExtraHTTPHeadersViaPlaywright({
235
+ cdpUrl,
236
+ targetId: tab.targetId,
237
+ headers: parsed,
238
+ });
239
+ res.json({ ok: true, targetId: tab.targetId });
240
+ },
241
+ });
222
242
  });
223
243
  app.post("/set/credentials", async (req, res) => {
224
- const profileCtx = resolveProfileContext(req, res, ctx);
225
- if (!profileCtx)
226
- return;
227
244
  const body = readBody(req);
228
- const targetId = toStringOrEmpty(body.targetId) || undefined;
245
+ const targetId = resolveTargetIdFromBody(body);
229
246
  const clear = toBoolean(body.clear) ?? false;
230
247
  const username = toStringOrEmpty(body.username) || undefined;
231
248
  const password = typeof body.password === "string" ? body.password : undefined;
232
- try {
233
- const tab = await profileCtx.ensureTabAvailable(targetId);
234
- const pw = await requirePwAi(res, "http credentials");
235
- if (!pw)
236
- return;
237
- await pw.setHttpCredentialsViaPlaywright({
238
- cdpUrl: profileCtx.profile.cdpUrl,
239
- targetId: tab.targetId,
240
- username,
241
- password,
242
- clear,
243
- });
244
- res.json({ ok: true, targetId: tab.targetId });
245
- }
246
- catch (err) {
247
- handleRouteError(ctx, res, err);
248
- }
249
+ await withPlaywrightRouteContext({
250
+ req,
251
+ res,
252
+ ctx,
253
+ targetId,
254
+ feature: "http credentials",
255
+ run: async ({ cdpUrl, tab, pw }) => {
256
+ await pw.setHttpCredentialsViaPlaywright({
257
+ cdpUrl,
258
+ targetId: tab.targetId,
259
+ username,
260
+ password,
261
+ clear,
262
+ });
263
+ res.json({ ok: true, targetId: tab.targetId });
264
+ },
265
+ });
249
266
  });
250
267
  app.post("/set/geolocation", async (req, res) => {
251
- const profileCtx = resolveProfileContext(req, res, ctx);
252
- if (!profileCtx)
253
- return;
254
268
  const body = readBody(req);
255
- const targetId = toStringOrEmpty(body.targetId) || undefined;
269
+ const targetId = resolveTargetIdFromBody(body);
256
270
  const clear = toBoolean(body.clear) ?? false;
257
271
  const latitude = toNumber(body.latitude);
258
272
  const longitude = toNumber(body.longitude);
259
273
  const accuracy = toNumber(body.accuracy) ?? undefined;
260
274
  const origin = toStringOrEmpty(body.origin) || undefined;
261
- try {
262
- const tab = await profileCtx.ensureTabAvailable(targetId);
263
- const pw = await requirePwAi(res, "geolocation");
264
- if (!pw)
265
- return;
266
- await pw.setGeolocationViaPlaywright({
267
- cdpUrl: profileCtx.profile.cdpUrl,
268
- targetId: tab.targetId,
269
- latitude,
270
- longitude,
271
- accuracy,
272
- origin,
273
- clear,
274
- });
275
- res.json({ ok: true, targetId: tab.targetId });
276
- }
277
- catch (err) {
278
- handleRouteError(ctx, res, err);
279
- }
275
+ await withPlaywrightRouteContext({
276
+ req,
277
+ res,
278
+ ctx,
279
+ targetId,
280
+ feature: "geolocation",
281
+ run: async ({ cdpUrl, tab, pw }) => {
282
+ await pw.setGeolocationViaPlaywright({
283
+ cdpUrl,
284
+ targetId: tab.targetId,
285
+ latitude,
286
+ longitude,
287
+ accuracy,
288
+ origin,
289
+ clear,
290
+ });
291
+ res.json({ ok: true, targetId: tab.targetId });
292
+ },
293
+ });
280
294
  });
281
295
  app.post("/set/media", async (req, res) => {
282
- const profileCtx = resolveProfileContext(req, res, ctx);
283
- if (!profileCtx)
284
- return;
285
296
  const body = readBody(req);
286
- const targetId = toStringOrEmpty(body.targetId) || undefined;
297
+ const targetId = resolveTargetIdFromBody(body);
287
298
  const schemeRaw = toStringOrEmpty(body.colorScheme);
288
299
  const colorScheme = schemeRaw === "dark" || schemeRaw === "light" || schemeRaw === "no-preference"
289
300
  ? schemeRaw
290
301
  : schemeRaw === "none"
291
302
  ? null
292
303
  : undefined;
293
- if (colorScheme === undefined)
304
+ if (colorScheme === undefined) {
294
305
  return jsonError(res, 400, "colorScheme must be dark|light|no-preference|none");
295
- try {
296
- const tab = await profileCtx.ensureTabAvailable(targetId);
297
- const pw = await requirePwAi(res, "media emulation");
298
- if (!pw)
299
- return;
300
- await pw.emulateMediaViaPlaywright({
301
- cdpUrl: profileCtx.profile.cdpUrl,
302
- targetId: tab.targetId,
303
- colorScheme,
304
- });
305
- res.json({ ok: true, targetId: tab.targetId });
306
- }
307
- catch (err) {
308
- handleRouteError(ctx, res, err);
309
306
  }
307
+ await withPlaywrightRouteContext({
308
+ req,
309
+ res,
310
+ ctx,
311
+ targetId,
312
+ feature: "media emulation",
313
+ run: async ({ cdpUrl, tab, pw }) => {
314
+ await pw.emulateMediaViaPlaywright({
315
+ cdpUrl,
316
+ targetId: tab.targetId,
317
+ colorScheme,
318
+ });
319
+ res.json({ ok: true, targetId: tab.targetId });
320
+ },
321
+ });
310
322
  });
311
323
  app.post("/set/timezone", async (req, res) => {
312
- const profileCtx = resolveProfileContext(req, res, ctx);
313
- if (!profileCtx)
314
- return;
315
324
  const body = readBody(req);
316
- const targetId = toStringOrEmpty(body.targetId) || undefined;
325
+ const targetId = resolveTargetIdFromBody(body);
317
326
  const timezoneId = toStringOrEmpty(body.timezoneId);
318
- if (!timezoneId)
327
+ if (!timezoneId) {
319
328
  return jsonError(res, 400, "timezoneId is required");
320
- try {
321
- const tab = await profileCtx.ensureTabAvailable(targetId);
322
- const pw = await requirePwAi(res, "timezone");
323
- if (!pw)
324
- return;
325
- await pw.setTimezoneViaPlaywright({
326
- cdpUrl: profileCtx.profile.cdpUrl,
327
- targetId: tab.targetId,
328
- timezoneId,
329
- });
330
- res.json({ ok: true, targetId: tab.targetId });
331
- }
332
- catch (err) {
333
- handleRouteError(ctx, res, err);
334
329
  }
330
+ await withPlaywrightRouteContext({
331
+ req,
332
+ res,
333
+ ctx,
334
+ targetId,
335
+ feature: "timezone",
336
+ run: async ({ cdpUrl, tab, pw }) => {
337
+ await pw.setTimezoneViaPlaywright({
338
+ cdpUrl,
339
+ targetId: tab.targetId,
340
+ timezoneId,
341
+ });
342
+ res.json({ ok: true, targetId: tab.targetId });
343
+ },
344
+ });
335
345
  });
336
346
  app.post("/set/locale", async (req, res) => {
337
- const profileCtx = resolveProfileContext(req, res, ctx);
338
- if (!profileCtx)
339
- return;
340
347
  const body = readBody(req);
341
- const targetId = toStringOrEmpty(body.targetId) || undefined;
348
+ const targetId = resolveTargetIdFromBody(body);
342
349
  const locale = toStringOrEmpty(body.locale);
343
- if (!locale)
350
+ if (!locale) {
344
351
  return jsonError(res, 400, "locale is required");
345
- try {
346
- const tab = await profileCtx.ensureTabAvailable(targetId);
347
- const pw = await requirePwAi(res, "locale");
348
- if (!pw)
349
- return;
350
- await pw.setLocaleViaPlaywright({
351
- cdpUrl: profileCtx.profile.cdpUrl,
352
- targetId: tab.targetId,
353
- locale,
354
- });
355
- res.json({ ok: true, targetId: tab.targetId });
356
- }
357
- catch (err) {
358
- handleRouteError(ctx, res, err);
359
352
  }
353
+ await withPlaywrightRouteContext({
354
+ req,
355
+ res,
356
+ ctx,
357
+ targetId,
358
+ feature: "locale",
359
+ run: async ({ cdpUrl, tab, pw }) => {
360
+ await pw.setLocaleViaPlaywright({
361
+ cdpUrl,
362
+ targetId: tab.targetId,
363
+ locale,
364
+ });
365
+ res.json({ ok: true, targetId: tab.targetId });
366
+ },
367
+ });
360
368
  });
361
369
  app.post("/set/device", async (req, res) => {
362
- const profileCtx = resolveProfileContext(req, res, ctx);
363
- if (!profileCtx)
364
- return;
365
370
  const body = readBody(req);
366
- const targetId = toStringOrEmpty(body.targetId) || undefined;
371
+ const targetId = resolveTargetIdFromBody(body);
367
372
  const name = toStringOrEmpty(body.name);
368
- if (!name)
373
+ if (!name) {
369
374
  return jsonError(res, 400, "name is required");
370
- try {
371
- const tab = await profileCtx.ensureTabAvailable(targetId);
372
- const pw = await requirePwAi(res, "device emulation");
373
- if (!pw)
374
- return;
375
- await pw.setDeviceViaPlaywright({
376
- cdpUrl: profileCtx.profile.cdpUrl,
377
- targetId: tab.targetId,
378
- name,
379
- });
380
- res.json({ ok: true, targetId: tab.targetId });
381
- }
382
- catch (err) {
383
- handleRouteError(ctx, res, err);
384
375
  }
376
+ await withPlaywrightRouteContext({
377
+ req,
378
+ res,
379
+ ctx,
380
+ targetId,
381
+ feature: "device emulation",
382
+ run: async ({ cdpUrl, tab, pw }) => {
383
+ await pw.setDeviceViaPlaywright({
384
+ cdpUrl,
385
+ targetId: tab.targetId,
386
+ name,
387
+ });
388
+ res.json({ ok: true, targetId: tab.targetId });
389
+ },
390
+ });
385
391
  });
386
392
  }