@poolzin/pool-bot 2026.2.24 → 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 +21 -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/sessions.test-helpers.js +61 -0
- 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/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/feishu/src/external-keys.ts +19 -0
- package/extensions/lobster/src/windows-spawn.ts +193 -0
- package/extensions/matrix/src/matrix/actions/limits.ts +6 -0
- package/extensions/mattermost/src/mattermost/reactions.test-helpers.ts +83 -0
- package/package.json +1 -1
|
@@ -1,36 +1,182 @@
|
|
|
1
|
-
import { approveNodePairing, listNodePairing, rejectNodePairing, renamePairedNode, requestNodePairing, verifyNodeToken, } from "../../infra/node-pairing.js";
|
|
2
|
-
import { listDevicePairing } from "../../infra/device-pairing.js";
|
|
3
|
-
import { ErrorCodes, errorShape, validateNodeDescribeParams, validateNodeEventParams, validateNodeInvokeParams, validateNodeInvokeResultParams, validateNodeListParams, validateNodePairApproveParams, validateNodePairListParams, validateNodePairRejectParams, validateNodePairRequestParams, validateNodePairVerifyParams, validateNodeRenameParams, } from "../protocol/index.js";
|
|
4
|
-
import { respondInvalidParams, respondUnavailableOnThrow, safeParseJson, uniqueSortedStrings, } from "./nodes.helpers.js";
|
|
5
1
|
import { loadConfig } from "../../config/config.js";
|
|
2
|
+
import { listDevicePairing } from "../../infra/device-pairing.js";
|
|
3
|
+
import { approveNodePairing, listNodePairing, rejectNodePairing, renamePairedNode, requestNodePairing, verifyNodeToken, } from "../../infra/node-pairing.js";
|
|
4
|
+
import { loadApnsRegistration, resolveApnsAuthConfigFromEnv, sendApnsAlert, sendApnsBackgroundWake, } from "../../infra/push-apns.js";
|
|
6
5
|
import { isNodeCommandAllowed, resolveNodeCommandAllowlist } from "../node-command-policy.js";
|
|
6
|
+
import { sanitizeNodeInvokeParamsForForwarding } from "../node-invoke-sanitize.js";
|
|
7
|
+
import { ErrorCodes, errorShape, validateNodeDescribeParams, validateNodeEventParams, validateNodeInvokeParams, validateNodeListParams, validateNodePairApproveParams, validateNodePairListParams, validateNodePairRejectParams, validateNodePairRequestParams, validateNodePairVerifyParams, validateNodeRenameParams, } from "../protocol/index.js";
|
|
8
|
+
import { handleNodeInvokeResult } from "./nodes.handlers.invoke-result.js";
|
|
9
|
+
import { respondInvalidParams, respondUnavailableOnNodeInvokeError, respondUnavailableOnThrow, safeParseJson, uniqueSortedStrings, } from "./nodes.helpers.js";
|
|
10
|
+
const NODE_WAKE_RECONNECT_WAIT_MS = 3_000;
|
|
11
|
+
const NODE_WAKE_RECONNECT_RETRY_WAIT_MS = 12_000;
|
|
12
|
+
const NODE_WAKE_RECONNECT_POLL_MS = 150;
|
|
13
|
+
const NODE_WAKE_THROTTLE_MS = 15_000;
|
|
14
|
+
const NODE_WAKE_NUDGE_THROTTLE_MS = 10 * 60_000;
|
|
15
|
+
const nodeWakeById = new Map();
|
|
16
|
+
const nodeWakeNudgeById = new Map();
|
|
7
17
|
function isNodeEntry(entry) {
|
|
8
|
-
if (entry.
|
|
18
|
+
if (entry.role === "node") {
|
|
9
19
|
return true;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if (Array.isArray(entry.roles) && entry.roles.includes("node"))
|
|
20
|
+
}
|
|
21
|
+
if (Array.isArray(entry.roles) && entry.roles.includes("node")) {
|
|
13
22
|
return true;
|
|
23
|
+
}
|
|
14
24
|
return false;
|
|
15
25
|
}
|
|
16
|
-
function
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
async function delayMs(ms) {
|
|
27
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
28
|
+
}
|
|
29
|
+
async function maybeWakeNodeWithApns(nodeId, opts) {
|
|
30
|
+
const state = nodeWakeById.get(nodeId) ?? { lastWakeAtMs: 0 };
|
|
31
|
+
nodeWakeById.set(nodeId, state);
|
|
32
|
+
if (state.inFlight) {
|
|
33
|
+
return await state.inFlight;
|
|
34
|
+
}
|
|
35
|
+
const now = Date.now();
|
|
36
|
+
const force = opts?.force === true;
|
|
37
|
+
if (!force && state.lastWakeAtMs > 0 && now - state.lastWakeAtMs < NODE_WAKE_THROTTLE_MS) {
|
|
38
|
+
return { available: true, throttled: true, path: "throttled", durationMs: 0 };
|
|
39
|
+
}
|
|
40
|
+
state.inFlight = (async () => {
|
|
41
|
+
const startedAtMs = Date.now();
|
|
42
|
+
const withDuration = (attempt) => ({
|
|
43
|
+
...attempt,
|
|
44
|
+
durationMs: Math.max(0, Date.now() - startedAtMs),
|
|
45
|
+
});
|
|
46
|
+
try {
|
|
47
|
+
const registration = await loadApnsRegistration(nodeId);
|
|
48
|
+
if (!registration) {
|
|
49
|
+
return withDuration({ available: false, throttled: false, path: "no-registration" });
|
|
50
|
+
}
|
|
51
|
+
const auth = await resolveApnsAuthConfigFromEnv(process.env);
|
|
52
|
+
if (!auth.ok) {
|
|
53
|
+
return withDuration({
|
|
54
|
+
available: false,
|
|
55
|
+
throttled: false,
|
|
56
|
+
path: "no-auth",
|
|
57
|
+
apnsReason: auth.error,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
state.lastWakeAtMs = Date.now();
|
|
61
|
+
const wakeResult = await sendApnsBackgroundWake({
|
|
62
|
+
auth: auth.value,
|
|
63
|
+
registration,
|
|
64
|
+
nodeId,
|
|
65
|
+
wakeReason: "node.invoke",
|
|
66
|
+
});
|
|
67
|
+
if (!wakeResult.ok) {
|
|
68
|
+
return withDuration({
|
|
69
|
+
available: true,
|
|
70
|
+
throttled: false,
|
|
71
|
+
path: "send-error",
|
|
72
|
+
apnsStatus: wakeResult.status,
|
|
73
|
+
apnsReason: wakeResult.reason,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return withDuration({
|
|
77
|
+
available: true,
|
|
78
|
+
throttled: false,
|
|
79
|
+
path: "sent",
|
|
80
|
+
apnsStatus: wakeResult.status,
|
|
81
|
+
apnsReason: wakeResult.reason,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
// Best-effort wake only.
|
|
86
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
87
|
+
if (state.lastWakeAtMs === 0) {
|
|
88
|
+
return withDuration({
|
|
89
|
+
available: false,
|
|
90
|
+
throttled: false,
|
|
91
|
+
path: "send-error",
|
|
92
|
+
apnsReason: message,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return withDuration({
|
|
96
|
+
available: true,
|
|
97
|
+
throttled: false,
|
|
98
|
+
path: "send-error",
|
|
99
|
+
apnsReason: message,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
})();
|
|
103
|
+
try {
|
|
104
|
+
return await state.inFlight;
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
state.inFlight = undefined;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async function maybeSendNodeWakeNudge(nodeId) {
|
|
111
|
+
const startedAtMs = Date.now();
|
|
112
|
+
const withDuration = (attempt) => ({
|
|
113
|
+
...attempt,
|
|
114
|
+
durationMs: Math.max(0, Date.now() - startedAtMs),
|
|
115
|
+
});
|
|
116
|
+
const lastNudgeAtMs = nodeWakeNudgeById.get(nodeId) ?? 0;
|
|
117
|
+
if (lastNudgeAtMs > 0 && Date.now() - lastNudgeAtMs < NODE_WAKE_NUDGE_THROTTLE_MS) {
|
|
118
|
+
return withDuration({ sent: false, throttled: true, reason: "throttled" });
|
|
119
|
+
}
|
|
120
|
+
const registration = await loadApnsRegistration(nodeId);
|
|
121
|
+
if (!registration) {
|
|
122
|
+
return withDuration({ sent: false, throttled: false, reason: "no-registration" });
|
|
123
|
+
}
|
|
124
|
+
const auth = await resolveApnsAuthConfigFromEnv(process.env);
|
|
125
|
+
if (!auth.ok) {
|
|
126
|
+
return withDuration({
|
|
127
|
+
sent: false,
|
|
128
|
+
throttled: false,
|
|
129
|
+
reason: "no-auth",
|
|
130
|
+
apnsReason: auth.error,
|
|
131
|
+
});
|
|
23
132
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
133
|
+
try {
|
|
134
|
+
const result = await sendApnsAlert({
|
|
135
|
+
auth: auth.value,
|
|
136
|
+
registration,
|
|
137
|
+
nodeId,
|
|
138
|
+
title: "Pool Bot needs a quick reopen",
|
|
139
|
+
body: "Tap to reopen Pool Bot and restore the node connection.",
|
|
140
|
+
});
|
|
141
|
+
if (!result.ok) {
|
|
142
|
+
return withDuration({
|
|
143
|
+
sent: false,
|
|
144
|
+
throttled: false,
|
|
145
|
+
reason: "apns-not-ok",
|
|
146
|
+
apnsStatus: result.status,
|
|
147
|
+
apnsReason: result.reason,
|
|
148
|
+
});
|
|
27
149
|
}
|
|
28
|
-
|
|
150
|
+
nodeWakeNudgeById.set(nodeId, Date.now());
|
|
151
|
+
return withDuration({
|
|
152
|
+
sent: true,
|
|
153
|
+
throttled: false,
|
|
154
|
+
reason: "sent",
|
|
155
|
+
apnsStatus: result.status,
|
|
156
|
+
apnsReason: result.reason,
|
|
157
|
+
});
|
|
29
158
|
}
|
|
30
|
-
|
|
31
|
-
|
|
159
|
+
catch (err) {
|
|
160
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
161
|
+
return withDuration({
|
|
162
|
+
sent: false,
|
|
163
|
+
throttled: false,
|
|
164
|
+
reason: "send-error",
|
|
165
|
+
apnsReason: message,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
async function waitForNodeReconnect(params) {
|
|
170
|
+
const timeoutMs = Math.max(250, params.timeoutMs ?? NODE_WAKE_RECONNECT_WAIT_MS);
|
|
171
|
+
const pollMs = Math.max(50, params.pollMs ?? NODE_WAKE_RECONNECT_POLL_MS);
|
|
172
|
+
const deadline = Date.now() + timeoutMs;
|
|
173
|
+
while (Date.now() < deadline) {
|
|
174
|
+
if (params.context.nodeRegistry.get(params.nodeId)) {
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
await delayMs(pollMs);
|
|
32
178
|
}
|
|
33
|
-
return
|
|
179
|
+
return Boolean(params.context.nodeRegistry.get(params.nodeId));
|
|
34
180
|
}
|
|
35
181
|
export const nodeHandlers = {
|
|
36
182
|
"node.pair.request": async ({ params, respond, context }) => {
|
|
@@ -227,14 +373,17 @@ export const nodeHandlers = {
|
|
|
227
373
|
};
|
|
228
374
|
});
|
|
229
375
|
nodes.sort((a, b) => {
|
|
230
|
-
if (a.connected !== b.connected)
|
|
376
|
+
if (a.connected !== b.connected) {
|
|
231
377
|
return a.connected ? -1 : 1;
|
|
378
|
+
}
|
|
232
379
|
const an = (a.displayName ?? a.nodeId).toLowerCase();
|
|
233
380
|
const bn = (b.displayName ?? b.nodeId).toLowerCase();
|
|
234
|
-
if (an < bn)
|
|
381
|
+
if (an < bn) {
|
|
235
382
|
return -1;
|
|
236
|
-
|
|
383
|
+
}
|
|
384
|
+
if (an > bn) {
|
|
237
385
|
return 1;
|
|
386
|
+
}
|
|
238
387
|
return a.nodeId.localeCompare(b.nodeId);
|
|
239
388
|
});
|
|
240
389
|
respond(true, { ts: Date.now(), nodes }, undefined);
|
|
@@ -287,7 +436,7 @@ export const nodeHandlers = {
|
|
|
287
436
|
}, undefined);
|
|
288
437
|
});
|
|
289
438
|
},
|
|
290
|
-
"node.invoke": async ({ params, respond, context }) => {
|
|
439
|
+
"node.invoke": async ({ params, respond, context, client, req }) => {
|
|
291
440
|
if (!validateNodeInvokeParams(params)) {
|
|
292
441
|
respondInvalidParams({
|
|
293
442
|
respond,
|
|
@@ -303,13 +452,69 @@ export const nodeHandlers = {
|
|
|
303
452
|
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "nodeId and command required"));
|
|
304
453
|
return;
|
|
305
454
|
}
|
|
455
|
+
if (command === "system.execApprovals.get" || command === "system.execApprovals.set") {
|
|
456
|
+
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "node.invoke does not allow system.execApprovals.*; use exec.approvals.node.*", { details: { command } }));
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
306
459
|
await respondUnavailableOnThrow(respond, async () => {
|
|
307
|
-
|
|
460
|
+
let nodeSession = context.nodeRegistry.get(nodeId);
|
|
308
461
|
if (!nodeSession) {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
})
|
|
312
|
-
|
|
462
|
+
const wakeReqId = req.id;
|
|
463
|
+
const wakeFlowStartedAtMs = Date.now();
|
|
464
|
+
context.logGateway.info(`node wake start node=${nodeId} req=${wakeReqId} command=${command}`);
|
|
465
|
+
const wake = await maybeWakeNodeWithApns(nodeId);
|
|
466
|
+
context.logGateway.info(`node wake stage=wake1 node=${nodeId} req=${wakeReqId} ` +
|
|
467
|
+
`available=${wake.available} throttled=${wake.throttled} ` +
|
|
468
|
+
`path=${wake.path} durationMs=${wake.durationMs} ` +
|
|
469
|
+
`apnsStatus=${wake.apnsStatus ?? -1} apnsReason=${wake.apnsReason ?? "-"}`);
|
|
470
|
+
if (wake.available) {
|
|
471
|
+
const waitStartedAtMs = Date.now();
|
|
472
|
+
const waitTimeoutMs = NODE_WAKE_RECONNECT_WAIT_MS;
|
|
473
|
+
const reconnected = await waitForNodeReconnect({
|
|
474
|
+
nodeId,
|
|
475
|
+
context,
|
|
476
|
+
timeoutMs: waitTimeoutMs,
|
|
477
|
+
});
|
|
478
|
+
const waitDurationMs = Math.max(0, Date.now() - waitStartedAtMs);
|
|
479
|
+
context.logGateway.info(`node wake stage=wait1 node=${nodeId} req=${wakeReqId} ` +
|
|
480
|
+
`reconnected=${reconnected} timeoutMs=${waitTimeoutMs} durationMs=${waitDurationMs}`);
|
|
481
|
+
}
|
|
482
|
+
nodeSession = context.nodeRegistry.get(nodeId);
|
|
483
|
+
if (!nodeSession && wake.available) {
|
|
484
|
+
const retryWake = await maybeWakeNodeWithApns(nodeId, { force: true });
|
|
485
|
+
context.logGateway.info(`node wake stage=wake2 node=${nodeId} req=${wakeReqId} force=true ` +
|
|
486
|
+
`available=${retryWake.available} throttled=${retryWake.throttled} ` +
|
|
487
|
+
`path=${retryWake.path} durationMs=${retryWake.durationMs} ` +
|
|
488
|
+
`apnsStatus=${retryWake.apnsStatus ?? -1} apnsReason=${retryWake.apnsReason ?? "-"}`);
|
|
489
|
+
if (retryWake.available) {
|
|
490
|
+
const waitStartedAtMs = Date.now();
|
|
491
|
+
const waitTimeoutMs = NODE_WAKE_RECONNECT_RETRY_WAIT_MS;
|
|
492
|
+
const reconnected = await waitForNodeReconnect({
|
|
493
|
+
nodeId,
|
|
494
|
+
context,
|
|
495
|
+
timeoutMs: waitTimeoutMs,
|
|
496
|
+
});
|
|
497
|
+
const waitDurationMs = Math.max(0, Date.now() - waitStartedAtMs);
|
|
498
|
+
context.logGateway.info(`node wake stage=wait2 node=${nodeId} req=${wakeReqId} ` +
|
|
499
|
+
`reconnected=${reconnected} timeoutMs=${waitTimeoutMs} durationMs=${waitDurationMs}`);
|
|
500
|
+
}
|
|
501
|
+
nodeSession = context.nodeRegistry.get(nodeId);
|
|
502
|
+
}
|
|
503
|
+
if (!nodeSession) {
|
|
504
|
+
const totalDurationMs = Math.max(0, Date.now() - wakeFlowStartedAtMs);
|
|
505
|
+
const nudge = await maybeSendNodeWakeNudge(nodeId);
|
|
506
|
+
context.logGateway.info(`node wake nudge node=${nodeId} req=${wakeReqId} sent=${nudge.sent} ` +
|
|
507
|
+
`throttled=${nudge.throttled} reason=${nudge.reason} durationMs=${nudge.durationMs} ` +
|
|
508
|
+
`apnsStatus=${nudge.apnsStatus ?? -1} apnsReason=${nudge.apnsReason ?? "-"}`);
|
|
509
|
+
context.logGateway.warn(`node wake done node=${nodeId} req=${wakeReqId} connected=false ` +
|
|
510
|
+
`reason=not_connected totalMs=${totalDurationMs}`);
|
|
511
|
+
respond(false, undefined, errorShape(ErrorCodes.UNAVAILABLE, "node not connected", {
|
|
512
|
+
details: { code: "NOT_CONNECTED" },
|
|
513
|
+
}));
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
const totalDurationMs = Math.max(0, Date.now() - wakeFlowStartedAtMs);
|
|
517
|
+
context.logGateway.info(`node wake done node=${nodeId} req=${wakeReqId} connected=true totalMs=${totalDurationMs}`);
|
|
313
518
|
}
|
|
314
519
|
const cfg = loadConfig();
|
|
315
520
|
const allowlist = resolveNodeCommandAllowlist(cfg, nodeSession);
|
|
@@ -324,17 +529,26 @@ export const nodeHandlers = {
|
|
|
324
529
|
}));
|
|
325
530
|
return;
|
|
326
531
|
}
|
|
532
|
+
const forwardedParams = sanitizeNodeInvokeParamsForForwarding({
|
|
533
|
+
command,
|
|
534
|
+
rawParams: p.params,
|
|
535
|
+
client,
|
|
536
|
+
execApprovalManager: context.execApprovalManager,
|
|
537
|
+
});
|
|
538
|
+
if (!forwardedParams.ok) {
|
|
539
|
+
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, forwardedParams.message, {
|
|
540
|
+
details: forwardedParams.details ?? null,
|
|
541
|
+
}));
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
327
544
|
const res = await context.nodeRegistry.invoke({
|
|
328
545
|
nodeId,
|
|
329
546
|
command,
|
|
330
|
-
params:
|
|
547
|
+
params: forwardedParams.params,
|
|
331
548
|
timeoutMs: p.timeoutMs,
|
|
332
549
|
idempotencyKey: p.idempotencyKey,
|
|
333
550
|
});
|
|
334
|
-
if (!res
|
|
335
|
-
respond(false, undefined, errorShape(ErrorCodes.UNAVAILABLE, res.error?.message ?? "node invoke failed", {
|
|
336
|
-
details: { nodeError: res.error ?? null },
|
|
337
|
-
}));
|
|
551
|
+
if (!respondUnavailableOnNodeInvokeError(respond, res)) {
|
|
338
552
|
return;
|
|
339
553
|
}
|
|
340
554
|
const payload = res.payloadJSON ? safeParseJson(res.payloadJSON) : res.payload;
|
|
@@ -347,39 +561,7 @@ export const nodeHandlers = {
|
|
|
347
561
|
}, undefined);
|
|
348
562
|
});
|
|
349
563
|
},
|
|
350
|
-
"node.invoke.result":
|
|
351
|
-
const normalizedParams = normalizeNodeInvokeResultParams(params);
|
|
352
|
-
if (!validateNodeInvokeResultParams(normalizedParams)) {
|
|
353
|
-
respondInvalidParams({
|
|
354
|
-
respond,
|
|
355
|
-
method: "node.invoke.result",
|
|
356
|
-
validator: validateNodeInvokeResultParams,
|
|
357
|
-
});
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
const p = normalizedParams;
|
|
361
|
-
const callerNodeId = client?.connect?.device?.id ?? client?.connect?.client?.id;
|
|
362
|
-
if (callerNodeId && callerNodeId !== p.nodeId) {
|
|
363
|
-
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "nodeId mismatch"));
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
const ok = context.nodeRegistry.handleInvokeResult({
|
|
367
|
-
id: p.id,
|
|
368
|
-
nodeId: p.nodeId,
|
|
369
|
-
ok: p.ok,
|
|
370
|
-
payload: p.payload,
|
|
371
|
-
payloadJSON: p.payloadJSON ?? null,
|
|
372
|
-
error: p.error ?? null,
|
|
373
|
-
});
|
|
374
|
-
if (!ok) {
|
|
375
|
-
// Late-arriving results (after invoke timeout) are expected and harmless.
|
|
376
|
-
// Return success instead of error to reduce log noise; client can discard.
|
|
377
|
-
context.logGateway.debug(`late invoke result ignored: id=${p.id} node=${p.nodeId}`);
|
|
378
|
-
respond(true, { ok: true, ignored: true }, undefined);
|
|
379
|
-
return;
|
|
380
|
-
}
|
|
381
|
-
respond(true, { ok: true }, undefined);
|
|
382
|
-
},
|
|
564
|
+
"node.invoke.result": handleNodeInvokeResult,
|
|
383
565
|
"node.event": async ({ params, respond, context, client }) => {
|
|
384
566
|
if (!validateNodeEventParams(params)) {
|
|
385
567
|
respondInvalidParams({
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { loadApnsRegistration, normalizeApnsEnvironment, resolveApnsAuthConfigFromEnv, sendApnsAlert, } from "../../infra/push-apns.js";
|
|
2
|
+
import { ErrorCodes, errorShape, validatePushTestParams } from "../protocol/index.js";
|
|
3
|
+
import { respondInvalidParams, respondUnavailableOnThrow } from "./nodes.helpers.js";
|
|
4
|
+
function normalizeOptionalString(value) {
|
|
5
|
+
if (typeof value !== "string") {
|
|
6
|
+
return undefined;
|
|
7
|
+
}
|
|
8
|
+
const trimmed = value.trim();
|
|
9
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
10
|
+
}
|
|
11
|
+
export const pushHandlers = {
|
|
12
|
+
"push.test": async ({ params, respond }) => {
|
|
13
|
+
if (!validatePushTestParams(params)) {
|
|
14
|
+
respondInvalidParams({
|
|
15
|
+
respond,
|
|
16
|
+
method: "push.test",
|
|
17
|
+
validator: validatePushTestParams,
|
|
18
|
+
});
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const nodeId = String(params.nodeId ?? "").trim();
|
|
22
|
+
if (!nodeId) {
|
|
23
|
+
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "nodeId required"));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const title = normalizeOptionalString(params.title) ?? "Pool Bot";
|
|
27
|
+
const body = normalizeOptionalString(params.body) ?? `Push test for node ${nodeId}`;
|
|
28
|
+
await respondUnavailableOnThrow(respond, async () => {
|
|
29
|
+
const registration = await loadApnsRegistration(nodeId);
|
|
30
|
+
if (!registration) {
|
|
31
|
+
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, `node ${nodeId} has no APNs registration (connect iOS node first)`));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const auth = await resolveApnsAuthConfigFromEnv(process.env);
|
|
35
|
+
if (!auth.ok) {
|
|
36
|
+
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, auth.error));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const overrideEnvironment = normalizeApnsEnvironment(params.environment);
|
|
40
|
+
const result = await sendApnsAlert({
|
|
41
|
+
auth: auth.value,
|
|
42
|
+
registration: {
|
|
43
|
+
...registration,
|
|
44
|
+
environment: overrideEnvironment ?? registration.environment,
|
|
45
|
+
},
|
|
46
|
+
nodeId,
|
|
47
|
+
title,
|
|
48
|
+
body,
|
|
49
|
+
});
|
|
50
|
+
respond(true, result, undefined);
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { startGmailWatcher, stopGmailWatcher } from "../hooks/gmail-watcher.js";
|
|
2
2
|
import { resetDirectoryCache } from "../infra/outbound/target-resolver.js";
|
|
3
|
-
import {
|
|
3
|
+
import { emitGatewayRestart, setGatewaySigusr1RestartPolicy } from "../infra/restart.js";
|
|
4
4
|
import { setCommandLaneConcurrency } from "../process/command-queue.js";
|
|
5
5
|
import { resolveAgentMaxConcurrent, resolveSubagentMaxConcurrent } from "../config/agent-limits.js";
|
|
6
6
|
import { isTruthyEnvValue } from "../infra/env.js";
|
|
@@ -105,8 +105,7 @@ export function createGatewayReloadHandlers(params) {
|
|
|
105
105
|
params.logReload.warn("no SIGUSR1 listener found; restart skipped");
|
|
106
106
|
return;
|
|
107
107
|
}
|
|
108
|
-
|
|
109
|
-
process.emit("SIGUSR1");
|
|
108
|
+
emitGatewayRestart();
|
|
110
109
|
};
|
|
111
110
|
return { applyHotReload, requestGatewayRestart };
|
|
112
111
|
}
|
|
@@ -13,6 +13,10 @@ export async function resolveGatewayRuntimeConfig(params) {
|
|
|
13
13
|
const openResponsesConfig = params.cfg.gateway?.http?.endpoints?.responses;
|
|
14
14
|
const openResponsesEnabled = params.openResponsesEnabled ?? openResponsesConfig?.enabled ?? false;
|
|
15
15
|
const controlUiBasePath = normalizeControlUiBasePath(params.cfg.gateway?.controlUi?.basePath);
|
|
16
|
+
const controlUiRootRaw = params.cfg.gateway?.controlUi?.root;
|
|
17
|
+
const controlUiRoot = typeof controlUiRootRaw === "string" && controlUiRootRaw.trim().length > 0
|
|
18
|
+
? controlUiRootRaw.trim()
|
|
19
|
+
: undefined;
|
|
16
20
|
const authBase = params.cfg.gateway?.auth ?? {};
|
|
17
21
|
const authOverrides = params.auth ?? {};
|
|
18
22
|
const authConfig = {
|
|
@@ -56,6 +60,7 @@ export async function resolveGatewayRuntimeConfig(params) {
|
|
|
56
60
|
? { ...openResponsesConfig, enabled: openResponsesEnabled }
|
|
57
61
|
: undefined,
|
|
58
62
|
controlUiBasePath,
|
|
63
|
+
controlUiRoot,
|
|
59
64
|
resolvedAuth,
|
|
60
65
|
authMode,
|
|
61
66
|
tailscaleConfig,
|
|
@@ -60,6 +60,7 @@ export async function createGatewayRuntimeState(params) {
|
|
|
60
60
|
handleHooksRequest,
|
|
61
61
|
handlePluginRequest,
|
|
62
62
|
resolvedAuth: params.resolvedAuth,
|
|
63
|
+
rateLimiter: params.rateLimiter,
|
|
63
64
|
tlsOptions: params.gatewayTls?.enabled ? params.gatewayTls.tlsOptions : undefined,
|
|
64
65
|
});
|
|
65
66
|
try {
|
|
@@ -93,6 +94,7 @@ export async function createGatewayRuntimeState(params) {
|
|
|
93
94
|
canvasHost,
|
|
94
95
|
clients,
|
|
95
96
|
resolvedAuth: params.resolvedAuth,
|
|
97
|
+
rateLimiter: params.rateLimiter,
|
|
96
98
|
});
|
|
97
99
|
}
|
|
98
100
|
const agentRunSeq = new Map();
|
|
@@ -8,6 +8,7 @@ export function attachGatewayWsHandlers(params) {
|
|
|
8
8
|
canvasHostEnabled: params.canvasHostEnabled,
|
|
9
9
|
canvasHostServerPort: params.canvasHostServerPort,
|
|
10
10
|
resolvedAuth: params.resolvedAuth,
|
|
11
|
+
rateLimiter: params.rateLimiter,
|
|
11
12
|
gatewayMethods: params.gatewayMethods,
|
|
12
13
|
events: params.events,
|
|
13
14
|
logGateway: params.logGateway,
|