@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,365 @@
|
|
|
1
|
+
import { createHash, createPrivateKey, sign as signJwt } from "node:crypto";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import http2 from "node:http2";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { resolveStateDir } from "../config/paths.js";
|
|
6
|
+
import { createAsyncLock, readJsonFile, writeJsonAtomic } from "./json-files.js";
|
|
7
|
+
const APNS_STATE_FILENAME = "push/apns-registrations.json";
|
|
8
|
+
const APNS_JWT_TTL_MS = 50 * 60 * 1000;
|
|
9
|
+
const DEFAULT_APNS_TIMEOUT_MS = 10_000;
|
|
10
|
+
const withLock = createAsyncLock();
|
|
11
|
+
let cachedJwt = null;
|
|
12
|
+
function resolveApnsRegistrationPath(baseDir) {
|
|
13
|
+
const root = baseDir ?? resolveStateDir();
|
|
14
|
+
return path.join(root, APNS_STATE_FILENAME);
|
|
15
|
+
}
|
|
16
|
+
function normalizeNodeId(value) {
|
|
17
|
+
return value.trim();
|
|
18
|
+
}
|
|
19
|
+
function normalizeApnsToken(value) {
|
|
20
|
+
return value
|
|
21
|
+
.trim()
|
|
22
|
+
.replace(/[<>\s]/g, "")
|
|
23
|
+
.toLowerCase();
|
|
24
|
+
}
|
|
25
|
+
function normalizeTopic(value) {
|
|
26
|
+
return value.trim();
|
|
27
|
+
}
|
|
28
|
+
function isLikelyApnsToken(value) {
|
|
29
|
+
return /^[0-9a-f]{32,}$/i.test(value);
|
|
30
|
+
}
|
|
31
|
+
function parseReason(body) {
|
|
32
|
+
const trimmed = body.trim();
|
|
33
|
+
if (!trimmed) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const parsed = JSON.parse(trimmed);
|
|
38
|
+
return typeof parsed.reason === "string" && parsed.reason.trim().length > 0
|
|
39
|
+
? parsed.reason.trim()
|
|
40
|
+
: trimmed.slice(0, 200);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return trimmed.slice(0, 200);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function toBase64UrlBytes(value) {
|
|
47
|
+
return Buffer.from(value)
|
|
48
|
+
.toString("base64")
|
|
49
|
+
.replace(/\+/g, "-")
|
|
50
|
+
.replace(/\//g, "_")
|
|
51
|
+
.replace(/=+$/g, "");
|
|
52
|
+
}
|
|
53
|
+
function toBase64UrlJson(value) {
|
|
54
|
+
return toBase64UrlBytes(Buffer.from(JSON.stringify(value)));
|
|
55
|
+
}
|
|
56
|
+
function getJwtCacheKey(auth) {
|
|
57
|
+
const keyHash = createHash("sha256").update(auth.privateKey).digest("hex");
|
|
58
|
+
return `${auth.teamId}:${auth.keyId}:${keyHash}`;
|
|
59
|
+
}
|
|
60
|
+
function getApnsBearerToken(auth, nowMs = Date.now()) {
|
|
61
|
+
const cacheKey = getJwtCacheKey(auth);
|
|
62
|
+
if (cachedJwt && cachedJwt.cacheKey === cacheKey && nowMs < cachedJwt.expiresAtMs) {
|
|
63
|
+
return cachedJwt.token;
|
|
64
|
+
}
|
|
65
|
+
const iat = Math.floor(nowMs / 1000);
|
|
66
|
+
const header = toBase64UrlJson({ alg: "ES256", kid: auth.keyId, typ: "JWT" });
|
|
67
|
+
const payload = toBase64UrlJson({ iss: auth.teamId, iat });
|
|
68
|
+
const signingInput = `${header}.${payload}`;
|
|
69
|
+
const signature = signJwt("sha256", Buffer.from(signingInput, "utf8"), {
|
|
70
|
+
key: createPrivateKey(auth.privateKey),
|
|
71
|
+
dsaEncoding: "ieee-p1363",
|
|
72
|
+
});
|
|
73
|
+
const token = `${signingInput}.${toBase64UrlBytes(signature)}`;
|
|
74
|
+
cachedJwt = {
|
|
75
|
+
cacheKey,
|
|
76
|
+
token,
|
|
77
|
+
expiresAtMs: nowMs + APNS_JWT_TTL_MS,
|
|
78
|
+
};
|
|
79
|
+
return token;
|
|
80
|
+
}
|
|
81
|
+
function normalizePrivateKey(value) {
|
|
82
|
+
return value.trim().replace(/\\n/g, "\n");
|
|
83
|
+
}
|
|
84
|
+
function normalizeNonEmptyString(value) {
|
|
85
|
+
const trimmed = value?.trim() ?? "";
|
|
86
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
87
|
+
}
|
|
88
|
+
async function loadRegistrationsState(baseDir) {
|
|
89
|
+
const filePath = resolveApnsRegistrationPath(baseDir);
|
|
90
|
+
const existing = await readJsonFile(filePath);
|
|
91
|
+
if (!existing || typeof existing !== "object") {
|
|
92
|
+
return { registrationsByNodeId: {} };
|
|
93
|
+
}
|
|
94
|
+
const registrations = existing.registrationsByNodeId &&
|
|
95
|
+
typeof existing.registrationsByNodeId === "object" &&
|
|
96
|
+
!Array.isArray(existing.registrationsByNodeId)
|
|
97
|
+
? existing.registrationsByNodeId
|
|
98
|
+
: {};
|
|
99
|
+
return { registrationsByNodeId: registrations };
|
|
100
|
+
}
|
|
101
|
+
async function persistRegistrationsState(state, baseDir) {
|
|
102
|
+
const filePath = resolveApnsRegistrationPath(baseDir);
|
|
103
|
+
await writeJsonAtomic(filePath, state);
|
|
104
|
+
}
|
|
105
|
+
export function normalizeApnsEnvironment(value) {
|
|
106
|
+
if (typeof value !== "string") {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
const normalized = value.trim().toLowerCase();
|
|
110
|
+
if (normalized === "sandbox" || normalized === "production") {
|
|
111
|
+
return normalized;
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
export async function registerApnsToken(params) {
|
|
116
|
+
const nodeId = normalizeNodeId(params.nodeId);
|
|
117
|
+
const token = normalizeApnsToken(params.token);
|
|
118
|
+
const topic = normalizeTopic(params.topic);
|
|
119
|
+
const environment = normalizeApnsEnvironment(params.environment) ?? "sandbox";
|
|
120
|
+
if (!nodeId) {
|
|
121
|
+
throw new Error("nodeId required");
|
|
122
|
+
}
|
|
123
|
+
if (!topic) {
|
|
124
|
+
throw new Error("topic required");
|
|
125
|
+
}
|
|
126
|
+
if (!isLikelyApnsToken(token)) {
|
|
127
|
+
throw new Error("invalid APNs token");
|
|
128
|
+
}
|
|
129
|
+
return await withLock(async () => {
|
|
130
|
+
const state = await loadRegistrationsState(params.baseDir);
|
|
131
|
+
const next = {
|
|
132
|
+
nodeId,
|
|
133
|
+
token,
|
|
134
|
+
topic,
|
|
135
|
+
environment,
|
|
136
|
+
updatedAtMs: Date.now(),
|
|
137
|
+
};
|
|
138
|
+
state.registrationsByNodeId[nodeId] = next;
|
|
139
|
+
await persistRegistrationsState(state, params.baseDir);
|
|
140
|
+
return next;
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
export async function loadApnsRegistration(nodeId, baseDir) {
|
|
144
|
+
const normalizedNodeId = normalizeNodeId(nodeId);
|
|
145
|
+
if (!normalizedNodeId) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
const state = await loadRegistrationsState(baseDir);
|
|
149
|
+
return state.registrationsByNodeId[normalizedNodeId] ?? null;
|
|
150
|
+
}
|
|
151
|
+
export async function resolveApnsAuthConfigFromEnv(env = process.env) {
|
|
152
|
+
const teamId = normalizeNonEmptyString(env.CLAWDBOT_APNS_TEAM_ID);
|
|
153
|
+
const keyId = normalizeNonEmptyString(env.CLAWDBOT_APNS_KEY_ID);
|
|
154
|
+
if (!teamId || !keyId) {
|
|
155
|
+
return {
|
|
156
|
+
ok: false,
|
|
157
|
+
error: "APNs auth missing: set CLAWDBOT_APNS_TEAM_ID and CLAWDBOT_APNS_KEY_ID",
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
const inlineKeyRaw = normalizeNonEmptyString(env.CLAWDBOT_APNS_PRIVATE_KEY_P8) ??
|
|
161
|
+
normalizeNonEmptyString(env.CLAWDBOT_APNS_PRIVATE_KEY);
|
|
162
|
+
if (inlineKeyRaw) {
|
|
163
|
+
return {
|
|
164
|
+
ok: true,
|
|
165
|
+
value: {
|
|
166
|
+
teamId,
|
|
167
|
+
keyId,
|
|
168
|
+
privateKey: normalizePrivateKey(inlineKeyRaw),
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
const keyPath = normalizeNonEmptyString(env.CLAWDBOT_APNS_PRIVATE_KEY_PATH);
|
|
173
|
+
if (!keyPath) {
|
|
174
|
+
return {
|
|
175
|
+
ok: false,
|
|
176
|
+
error: "APNs private key missing: set CLAWDBOT_APNS_PRIVATE_KEY_P8 or CLAWDBOT_APNS_PRIVATE_KEY_PATH",
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
const privateKey = normalizePrivateKey(await fs.readFile(keyPath, "utf8"));
|
|
181
|
+
return {
|
|
182
|
+
ok: true,
|
|
183
|
+
value: {
|
|
184
|
+
teamId,
|
|
185
|
+
keyId,
|
|
186
|
+
privateKey,
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
192
|
+
return {
|
|
193
|
+
ok: false,
|
|
194
|
+
error: `failed reading CLAWDBOT_APNS_PRIVATE_KEY_PATH (${keyPath}): ${message}`,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async function sendApnsRequest(params) {
|
|
199
|
+
const authority = params.environment === "production"
|
|
200
|
+
? "https://api.push.apple.com"
|
|
201
|
+
: "https://api.sandbox.push.apple.com";
|
|
202
|
+
const body = JSON.stringify(params.payload);
|
|
203
|
+
const requestPath = `/3/device/${params.token}`;
|
|
204
|
+
return await new Promise((resolve, reject) => {
|
|
205
|
+
const client = http2.connect(authority);
|
|
206
|
+
let settled = false;
|
|
207
|
+
const fail = (err) => {
|
|
208
|
+
if (settled) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
settled = true;
|
|
212
|
+
client.destroy();
|
|
213
|
+
reject(err);
|
|
214
|
+
};
|
|
215
|
+
const finish = (result) => {
|
|
216
|
+
if (settled) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
settled = true;
|
|
220
|
+
client.close();
|
|
221
|
+
resolve(result);
|
|
222
|
+
};
|
|
223
|
+
client.once("error", (err) => fail(err));
|
|
224
|
+
const req = client.request({
|
|
225
|
+
":method": "POST",
|
|
226
|
+
":path": requestPath,
|
|
227
|
+
authorization: `bearer ${params.bearerToken}`,
|
|
228
|
+
"apns-topic": params.topic,
|
|
229
|
+
"apns-push-type": params.pushType,
|
|
230
|
+
"apns-priority": params.priority,
|
|
231
|
+
"apns-expiration": "0",
|
|
232
|
+
"content-type": "application/json",
|
|
233
|
+
"content-length": Buffer.byteLength(body).toString(),
|
|
234
|
+
});
|
|
235
|
+
let statusCode = 0;
|
|
236
|
+
let apnsId;
|
|
237
|
+
let responseBody = "";
|
|
238
|
+
req.setEncoding("utf8");
|
|
239
|
+
req.setTimeout(params.timeoutMs, () => {
|
|
240
|
+
req.close(http2.constants.NGHTTP2_CANCEL);
|
|
241
|
+
fail(new Error(`APNs request timed out after ${params.timeoutMs}ms`));
|
|
242
|
+
});
|
|
243
|
+
req.on("response", (headers) => {
|
|
244
|
+
const statusHeader = headers[":status"];
|
|
245
|
+
statusCode = typeof statusHeader === "number" ? statusHeader : Number(statusHeader ?? 0);
|
|
246
|
+
const idHeader = headers["apns-id"];
|
|
247
|
+
if (typeof idHeader === "string" && idHeader.trim().length > 0) {
|
|
248
|
+
apnsId = idHeader.trim();
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
req.on("data", (chunk) => {
|
|
252
|
+
if (typeof chunk === "string") {
|
|
253
|
+
responseBody += chunk;
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
req.on("end", () => {
|
|
257
|
+
finish({ status: statusCode, apnsId, body: responseBody });
|
|
258
|
+
});
|
|
259
|
+
req.on("error", (err) => fail(err));
|
|
260
|
+
req.end(body);
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
function resolveApnsTimeoutMs(timeoutMs) {
|
|
264
|
+
return typeof timeoutMs === "number" && Number.isFinite(timeoutMs)
|
|
265
|
+
? Math.max(1000, Math.trunc(timeoutMs))
|
|
266
|
+
: DEFAULT_APNS_TIMEOUT_MS;
|
|
267
|
+
}
|
|
268
|
+
function resolveApnsSendContext(params) {
|
|
269
|
+
const token = normalizeApnsToken(params.registration.token);
|
|
270
|
+
if (!isLikelyApnsToken(token)) {
|
|
271
|
+
throw new Error("invalid APNs token");
|
|
272
|
+
}
|
|
273
|
+
const topic = normalizeTopic(params.registration.topic);
|
|
274
|
+
if (!topic) {
|
|
275
|
+
throw new Error("topic required");
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
token,
|
|
279
|
+
topic,
|
|
280
|
+
environment: params.registration.environment,
|
|
281
|
+
bearerToken: getApnsBearerToken(params.auth),
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
function toApnsPushResult(params) {
|
|
285
|
+
return {
|
|
286
|
+
ok: params.response.status === 200,
|
|
287
|
+
status: params.response.status,
|
|
288
|
+
apnsId: params.response.apnsId,
|
|
289
|
+
reason: parseReason(params.response.body),
|
|
290
|
+
tokenSuffix: params.token.slice(-8),
|
|
291
|
+
topic: params.topic,
|
|
292
|
+
environment: params.environment,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
function createPoolBotPushMetadata(params) {
|
|
296
|
+
return {
|
|
297
|
+
kind: params.kind,
|
|
298
|
+
nodeId: params.nodeId,
|
|
299
|
+
ts: Date.now(),
|
|
300
|
+
...(params.reason ? { reason: params.reason } : {}),
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
async function sendApnsPush(params) {
|
|
304
|
+
const { token, topic, environment, bearerToken } = resolveApnsSendContext({
|
|
305
|
+
auth: params.auth,
|
|
306
|
+
registration: params.registration,
|
|
307
|
+
});
|
|
308
|
+
const sender = params.requestSender ?? sendApnsRequest;
|
|
309
|
+
const response = await sender({
|
|
310
|
+
token,
|
|
311
|
+
topic,
|
|
312
|
+
environment,
|
|
313
|
+
bearerToken,
|
|
314
|
+
payload: params.payload,
|
|
315
|
+
timeoutMs: resolveApnsTimeoutMs(params.timeoutMs),
|
|
316
|
+
pushType: params.pushType,
|
|
317
|
+
priority: params.priority,
|
|
318
|
+
});
|
|
319
|
+
return toApnsPushResult({ response, token, topic, environment });
|
|
320
|
+
}
|
|
321
|
+
export async function sendApnsAlert(params) {
|
|
322
|
+
const payload = {
|
|
323
|
+
aps: {
|
|
324
|
+
alert: {
|
|
325
|
+
title: params.title,
|
|
326
|
+
body: params.body,
|
|
327
|
+
},
|
|
328
|
+
sound: "default",
|
|
329
|
+
},
|
|
330
|
+
poolbot: createPoolBotPushMetadata({
|
|
331
|
+
kind: "push.test",
|
|
332
|
+
nodeId: params.nodeId,
|
|
333
|
+
}),
|
|
334
|
+
};
|
|
335
|
+
return await sendApnsPush({
|
|
336
|
+
auth: params.auth,
|
|
337
|
+
registration: params.registration,
|
|
338
|
+
payload,
|
|
339
|
+
timeoutMs: params.timeoutMs,
|
|
340
|
+
requestSender: params.requestSender,
|
|
341
|
+
pushType: "alert",
|
|
342
|
+
priority: "10",
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
export async function sendApnsBackgroundWake(params) {
|
|
346
|
+
const payload = {
|
|
347
|
+
aps: {
|
|
348
|
+
"content-available": 1,
|
|
349
|
+
},
|
|
350
|
+
poolbot: createPoolBotPushMetadata({
|
|
351
|
+
kind: "node.wake",
|
|
352
|
+
reason: params.wakeReason ?? "node.invoke",
|
|
353
|
+
nodeId: params.nodeId,
|
|
354
|
+
}),
|
|
355
|
+
};
|
|
356
|
+
return await sendApnsPush({
|
|
357
|
+
auth: params.auth,
|
|
358
|
+
registration: params.registration,
|
|
359
|
+
payload,
|
|
360
|
+
timeoutMs: params.timeoutMs,
|
|
361
|
+
requestSender: params.requestSender,
|
|
362
|
+
pushType: "background",
|
|
363
|
+
priority: "5",
|
|
364
|
+
});
|
|
365
|
+
}
|
|
@@ -47,7 +47,22 @@ export async function consumeRestartSentinel(env = process.env) {
|
|
|
47
47
|
return parsed;
|
|
48
48
|
}
|
|
49
49
|
export function formatRestartSentinelMessage(payload) {
|
|
50
|
-
|
|
50
|
+
const message = payload.message?.trim();
|
|
51
|
+
if (message && !payload.stats) {
|
|
52
|
+
return message;
|
|
53
|
+
}
|
|
54
|
+
const lines = [summarizeRestartSentinel(payload)];
|
|
55
|
+
if (message) {
|
|
56
|
+
lines.push(message);
|
|
57
|
+
}
|
|
58
|
+
const reason = payload.stats?.reason?.trim();
|
|
59
|
+
if (reason) {
|
|
60
|
+
lines.push(`Reason: ${reason}`);
|
|
61
|
+
}
|
|
62
|
+
if (payload.doctorHint?.trim()) {
|
|
63
|
+
lines.push(payload.doctorHint.trim());
|
|
64
|
+
}
|
|
65
|
+
return lines.join("\n");
|
|
51
66
|
}
|
|
52
67
|
export function summarizeRestartSentinel(payload) {
|
|
53
68
|
const kind = payload.kind;
|