@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
@@ -23,8 +23,86 @@ export async function resolveArchiveSourcePath(archivePath) {
23
23
  }
24
24
  return { ok: true, path: resolved };
25
25
  }
26
+ function toOptionalString(value) {
27
+ if (typeof value !== "string") {
28
+ return undefined;
29
+ }
30
+ const trimmed = value.trim();
31
+ return trimmed.length > 0 ? trimmed : undefined;
32
+ }
33
+ function parseResolvedSpecFromId(id) {
34
+ const at = id.lastIndexOf("@");
35
+ if (at <= 0 || at >= id.length - 1) {
36
+ return undefined;
37
+ }
38
+ const name = id.slice(0, at).trim();
39
+ const version = id.slice(at + 1).trim();
40
+ if (!name || !version) {
41
+ return undefined;
42
+ }
43
+ return `${name}@${version}`;
44
+ }
45
+ function normalizeNpmPackEntry(entry) {
46
+ if (!entry || typeof entry !== "object") {
47
+ return null;
48
+ }
49
+ const rec = entry;
50
+ const name = toOptionalString(rec.name);
51
+ const version = toOptionalString(rec.version);
52
+ const id = toOptionalString(rec.id);
53
+ const resolvedSpec = (name && version ? `${name}@${version}` : undefined) ??
54
+ (id ? parseResolvedSpecFromId(id) : undefined);
55
+ return {
56
+ filename: toOptionalString(rec.filename),
57
+ metadata: {
58
+ name,
59
+ version,
60
+ resolvedSpec,
61
+ integrity: toOptionalString(rec.integrity),
62
+ shasum: toOptionalString(rec.shasum),
63
+ },
64
+ };
65
+ }
66
+ function parseNpmPackJsonOutput(raw) {
67
+ const trimmed = raw.trim();
68
+ if (!trimmed) {
69
+ return null;
70
+ }
71
+ const candidates = [trimmed];
72
+ const arrayStart = trimmed.indexOf("[");
73
+ if (arrayStart > 0) {
74
+ candidates.push(trimmed.slice(arrayStart));
75
+ }
76
+ for (const candidate of candidates) {
77
+ let parsed;
78
+ try {
79
+ parsed = JSON.parse(candidate);
80
+ }
81
+ catch {
82
+ continue;
83
+ }
84
+ const entries = Array.isArray(parsed) ? parsed : [parsed];
85
+ let fallback = null;
86
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
87
+ const normalized = normalizeNpmPackEntry(entries[i]);
88
+ if (!normalized) {
89
+ continue;
90
+ }
91
+ if (!fallback) {
92
+ fallback = normalized;
93
+ }
94
+ if (normalized.filename) {
95
+ return normalized;
96
+ }
97
+ }
98
+ if (fallback) {
99
+ return fallback;
100
+ }
101
+ }
102
+ return null;
103
+ }
26
104
  export async function packNpmSpecToArchive(params) {
27
- const res = await runCommandWithTimeout(["npm", "pack", params.spec, "--ignore-scripts"], {
105
+ const res = await runCommandWithTimeout(["npm", "pack", params.spec, "--ignore-scripts", "--json"], {
28
106
  timeoutMs: Math.max(params.timeoutMs, 300_000),
29
107
  cwd: params.cwd,
30
108
  env: {
@@ -35,13 +113,19 @@ export async function packNpmSpecToArchive(params) {
35
113
  if (res.code !== 0) {
36
114
  return { ok: false, error: `npm pack failed: ${res.stderr.trim() || res.stdout.trim()}` };
37
115
  }
38
- const packed = (res.stdout || "")
39
- .split("\n")
40
- .map((line) => line.trim())
41
- .filter(Boolean)
42
- .pop();
116
+ const parsedJson = parseNpmPackJsonOutput(res.stdout || "");
117
+ const packed = parsedJson?.filename ??
118
+ (res.stdout || "")
119
+ .split("\n")
120
+ .map((line) => line.trim())
121
+ .filter(Boolean)
122
+ .pop();
43
123
  if (!packed) {
44
124
  return { ok: false, error: "npm pack produced no archive" };
45
125
  }
46
- return { ok: true, archivePath: path.join(params.cwd, packed) };
126
+ return {
127
+ ok: true,
128
+ archivePath: path.join(params.cwd, packed),
129
+ metadata: parsedJson?.metadata ?? {},
130
+ };
47
131
  }
@@ -1,97 +1,38 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import fs from "node:fs/promises";
3
- import path from "node:path";
4
- import { resolveStateDir } from "../config/paths.js";
2
+ import { createAsyncLock, pruneExpiredPending, readJsonFile, resolvePairingPaths, upsertPendingPairingRequest, writeJsonAtomic, } from "./pairing-files.js";
3
+ import { generatePairingToken, verifyPairingToken } from "./pairing-token.js";
5
4
  const PENDING_TTL_MS = 5 * 60 * 1000;
6
- function resolvePaths(baseDir) {
7
- const root = baseDir ?? resolveStateDir();
8
- const dir = path.join(root, "nodes");
9
- return {
10
- dir,
11
- pendingPath: path.join(dir, "pending.json"),
12
- pairedPath: path.join(dir, "paired.json"),
13
- };
14
- }
15
- async function readJSON(filePath) {
16
- try {
17
- const raw = await fs.readFile(filePath, "utf8");
18
- return JSON.parse(raw);
19
- }
20
- catch {
21
- return null;
22
- }
23
- }
24
- async function writeJSONAtomic(filePath, value) {
25
- const dir = path.dirname(filePath);
26
- await fs.mkdir(dir, { recursive: true });
27
- const tmp = `${filePath}.${randomUUID()}.tmp`;
28
- await fs.writeFile(tmp, JSON.stringify(value, null, 2), "utf8");
29
- try {
30
- await fs.chmod(tmp, 0o600);
31
- }
32
- catch {
33
- // best-effort; ignore on platforms without chmod
34
- }
35
- await fs.rename(tmp, filePath);
36
- try {
37
- await fs.chmod(filePath, 0o600);
38
- }
39
- catch {
40
- // best-effort; ignore on platforms without chmod
41
- }
42
- }
43
- function pruneExpiredPending(pendingById, nowMs) {
44
- for (const [id, req] of Object.entries(pendingById)) {
45
- if (nowMs - req.ts > PENDING_TTL_MS) {
46
- delete pendingById[id];
47
- }
48
- }
49
- }
50
- let lock = Promise.resolve();
51
- async function withLock(fn) {
52
- const prev = lock;
53
- let release;
54
- lock = new Promise((resolve) => {
55
- release = resolve;
56
- });
57
- await prev;
58
- try {
59
- return await fn();
60
- }
61
- finally {
62
- release?.();
63
- }
64
- }
5
+ const withLock = createAsyncLock();
65
6
  async function loadState(baseDir) {
66
- const { pendingPath, pairedPath } = resolvePaths(baseDir);
7
+ const { pendingPath, pairedPath } = resolvePairingPaths(baseDir, "nodes");
67
8
  const [pending, paired] = await Promise.all([
68
- readJSON(pendingPath),
69
- readJSON(pairedPath),
9
+ readJsonFile(pendingPath),
10
+ readJsonFile(pairedPath),
70
11
  ]);
71
12
  const state = {
72
13
  pendingById: pending ?? {},
73
14
  pairedByNodeId: paired ?? {},
74
15
  };
75
- pruneExpiredPending(state.pendingById, Date.now());
16
+ pruneExpiredPending(state.pendingById, Date.now(), PENDING_TTL_MS);
76
17
  return state;
77
18
  }
78
19
  async function persistState(state, baseDir) {
79
- const { pendingPath, pairedPath } = resolvePaths(baseDir);
20
+ const { pendingPath, pairedPath } = resolvePairingPaths(baseDir, "nodes");
80
21
  await Promise.all([
81
- writeJSONAtomic(pendingPath, state.pendingById),
82
- writeJSONAtomic(pairedPath, state.pairedByNodeId),
22
+ writeJsonAtomic(pendingPath, state.pendingById),
23
+ writeJsonAtomic(pairedPath, state.pairedByNodeId),
83
24
  ]);
84
25
  }
85
26
  function normalizeNodeId(nodeId) {
86
27
  return nodeId.trim();
87
28
  }
88
29
  function newToken() {
89
- return randomUUID().replaceAll("-", "");
30
+ return generatePairingToken();
90
31
  }
91
32
  export async function listNodePairing(baseDir) {
92
33
  const state = await loadState(baseDir);
93
- const pending = Object.values(state.pendingById).sort((a, b) => b.ts - a.ts);
94
- const paired = Object.values(state.pairedByNodeId).sort((a, b) => b.approvedAtMs - a.approvedAtMs);
34
+ const pending = Object.values(state.pendingById).toSorted((a, b) => b.ts - a.ts);
35
+ const paired = Object.values(state.pairedByNodeId).toSorted((a, b) => b.approvedAtMs - a.approvedAtMs);
95
36
  return { pending, paired };
96
37
  }
97
38
  export async function getPairedNode(nodeId, baseDir) {
@@ -105,40 +46,39 @@ export async function requestNodePairing(req, baseDir) {
105
46
  if (!nodeId) {
106
47
  throw new Error("nodeId required");
107
48
  }
108
- const existing = Object.values(state.pendingById).find((p) => p.nodeId === nodeId);
109
- if (existing) {
110
- return { status: "pending", request: existing, created: false };
111
- }
112
- const isRepair = Boolean(state.pairedByNodeId[nodeId]);
113
- const request = {
114
- requestId: randomUUID(),
115
- nodeId,
116
- displayName: req.displayName,
117
- platform: req.platform,
118
- version: req.version,
119
- coreVersion: req.coreVersion,
120
- uiVersion: req.uiVersion,
121
- deviceFamily: req.deviceFamily,
122
- modelIdentifier: req.modelIdentifier,
123
- caps: req.caps,
124
- commands: req.commands,
125
- permissions: req.permissions,
126
- remoteIp: req.remoteIp,
127
- silent: req.silent,
128
- isRepair,
129
- ts: Date.now(),
130
- };
131
- state.pendingById[request.requestId] = request;
132
- await persistState(state, baseDir);
133
- return { status: "pending", request, created: true };
49
+ return await upsertPendingPairingRequest({
50
+ pendingById: state.pendingById,
51
+ isExisting: (pending) => pending.nodeId === nodeId,
52
+ isRepair: Boolean(state.pairedByNodeId[nodeId]),
53
+ createRequest: (isRepair) => ({
54
+ requestId: randomUUID(),
55
+ nodeId,
56
+ displayName: req.displayName,
57
+ platform: req.platform,
58
+ version: req.version,
59
+ coreVersion: req.coreVersion,
60
+ uiVersion: req.uiVersion,
61
+ deviceFamily: req.deviceFamily,
62
+ modelIdentifier: req.modelIdentifier,
63
+ caps: req.caps,
64
+ commands: req.commands,
65
+ permissions: req.permissions,
66
+ remoteIp: req.remoteIp,
67
+ silent: req.silent,
68
+ isRepair,
69
+ ts: Date.now(),
70
+ }),
71
+ persist: async () => await persistState(state, baseDir),
72
+ });
134
73
  });
135
74
  }
136
75
  export async function approveNodePairing(requestId, baseDir) {
137
76
  return await withLock(async () => {
138
77
  const state = await loadState(baseDir);
139
78
  const pending = state.pendingById[requestId];
140
- if (!pending)
79
+ if (!pending) {
141
80
  return null;
81
+ }
142
82
  const now = Date.now();
143
83
  const existing = state.pairedByNodeId[pending.nodeId];
144
84
  const node = {
@@ -168,8 +108,9 @@ export async function rejectNodePairing(requestId, baseDir) {
168
108
  return await withLock(async () => {
169
109
  const state = await loadState(baseDir);
170
110
  const pending = state.pendingById[requestId];
171
- if (!pending)
111
+ if (!pending) {
172
112
  return null;
113
+ }
173
114
  delete state.pendingById[requestId];
174
115
  await persistState(state, baseDir);
175
116
  return { requestId, nodeId: pending.nodeId };
@@ -179,17 +120,19 @@ export async function verifyNodeToken(nodeId, token, baseDir) {
179
120
  const state = await loadState(baseDir);
180
121
  const normalized = normalizeNodeId(nodeId);
181
122
  const node = state.pairedByNodeId[normalized];
182
- if (!node)
123
+ if (!node) {
183
124
  return { ok: false };
184
- return node.token === token ? { ok: true, node } : { ok: false };
125
+ }
126
+ return verifyPairingToken(token, node.token) ? { ok: true, node } : { ok: false };
185
127
  }
186
128
  export async function updatePairedNodeMetadata(nodeId, patch, baseDir) {
187
129
  await withLock(async () => {
188
130
  const state = await loadState(baseDir);
189
131
  const normalized = normalizeNodeId(nodeId);
190
132
  const existing = state.pairedByNodeId[normalized];
191
- if (!existing)
133
+ if (!existing) {
192
134
  return;
135
+ }
193
136
  const next = {
194
137
  ...existing,
195
138
  displayName: patch.displayName ?? existing.displayName,
@@ -215,11 +158,13 @@ export async function renamePairedNode(nodeId, displayName, baseDir) {
215
158
  const state = await loadState(baseDir);
216
159
  const normalized = normalizeNodeId(nodeId);
217
160
  const existing = state.pairedByNodeId[normalized];
218
- if (!existing)
161
+ if (!existing) {
219
162
  return null;
163
+ }
220
164
  const trimmed = displayName.trim();
221
- if (!trimmed)
165
+ if (!trimmed) {
222
166
  throw new Error("displayName required");
167
+ }
223
168
  const next = { ...existing, displayName: trimmed };
224
169
  state.pairedByNodeId[normalized] = next;
225
170
  await persistState(state, baseDir);
@@ -0,0 +1,45 @@
1
+ export async function resolveNpmIntegrityDrift(params) {
2
+ if (!params.expectedIntegrity || !params.resolution.integrity) {
3
+ return { proceed: true };
4
+ }
5
+ if (params.expectedIntegrity === params.resolution.integrity) {
6
+ return { proceed: true };
7
+ }
8
+ const integrityDrift = {
9
+ expectedIntegrity: params.expectedIntegrity,
10
+ actualIntegrity: params.resolution.integrity,
11
+ };
12
+ const payload = params.createPayload({
13
+ spec: params.spec,
14
+ expectedIntegrity: integrityDrift.expectedIntegrity,
15
+ actualIntegrity: integrityDrift.actualIntegrity,
16
+ resolution: params.resolution,
17
+ });
18
+ let proceed = true;
19
+ if (params.onIntegrityDrift) {
20
+ proceed = await params.onIntegrityDrift(payload);
21
+ }
22
+ else {
23
+ params.warn?.(payload);
24
+ }
25
+ return { integrityDrift, proceed, payload };
26
+ }
27
+ export async function resolveNpmIntegrityDriftWithDefaultMessage(params) {
28
+ const driftResult = await resolveNpmIntegrityDrift({
29
+ spec: params.spec,
30
+ expectedIntegrity: params.expectedIntegrity,
31
+ resolution: params.resolution,
32
+ createPayload: (drift) => ({ ...drift }),
33
+ onIntegrityDrift: params.onIntegrityDrift,
34
+ warn: (driftPayload) => {
35
+ params.warn?.(`Integrity drift detected for ${driftPayload.resolution.resolvedSpec ?? driftPayload.spec}: expected ${driftPayload.expectedIntegrity}, got ${driftPayload.actualIntegrity}`);
36
+ },
37
+ });
38
+ if (!driftResult.proceed && driftResult.payload) {
39
+ return {
40
+ integrityDrift: driftResult.integrityDrift,
41
+ error: `aborted: npm package integrity drift detected for ${driftResult.payload.resolution.resolvedSpec ?? driftResult.payload.spec}`,
42
+ };
43
+ }
44
+ return { integrityDrift: driftResult.integrityDrift };
45
+ }
@@ -0,0 +1,40 @@
1
+ import { packNpmSpecToArchive, withTempDir, } from "./install-source-utils.js";
2
+ import { resolveNpmIntegrityDriftWithDefaultMessage, } from "./npm-integrity.js";
3
+ export async function installFromNpmSpecArchive(params) {
4
+ return await withTempDir(params.tempDirPrefix, async (tmpDir) => {
5
+ const packedResult = await packNpmSpecToArchive({
6
+ spec: params.spec,
7
+ timeoutMs: params.timeoutMs,
8
+ cwd: tmpDir,
9
+ });
10
+ if (!packedResult.ok) {
11
+ return packedResult;
12
+ }
13
+ const npmResolution = {
14
+ ...packedResult.metadata,
15
+ resolvedAt: new Date().toISOString(),
16
+ };
17
+ const driftResult = await resolveNpmIntegrityDriftWithDefaultMessage({
18
+ spec: params.spec,
19
+ expectedIntegrity: params.expectedIntegrity,
20
+ resolution: npmResolution,
21
+ onIntegrityDrift: params.onIntegrityDrift,
22
+ warn: params.warn,
23
+ });
24
+ if (driftResult.error) {
25
+ return {
26
+ ok: false,
27
+ error: driftResult.error,
28
+ };
29
+ }
30
+ const installResult = await params.installFromArchive({
31
+ archivePath: packedResult.archivePath,
32
+ });
33
+ return {
34
+ ok: true,
35
+ installResult,
36
+ npmResolution,
37
+ integrityDrift: driftResult.integrityDrift,
38
+ };
39
+ });
40
+ }
@@ -1,16 +1,29 @@
1
+ import { Separator, TextDisplay } from "@buape/carbon";
2
+ import { DiscordUiContainer } from "../../discord/ui.js";
3
+ class CrossContextContainer extends DiscordUiContainer {
4
+ constructor({ originLabel, message, cfg, accountId }) {
5
+ const trimmed = message.trim();
6
+ const components = [];
7
+ if (trimmed) {
8
+ components.push(new TextDisplay(message));
9
+ components.push(new Separator({ divider: true, spacing: "small" }));
10
+ }
11
+ components.push(new TextDisplay(`*From ${originLabel}*`));
12
+ super({ cfg, accountId, components });
13
+ }
14
+ }
1
15
  const DEFAULT_ADAPTER = {
2
- supportsEmbeds: false,
16
+ supportsComponentsV2: false,
3
17
  };
4
18
  const DISCORD_ADAPTER = {
5
- supportsEmbeds: true,
6
- buildCrossContextEmbeds: (originLabel) => [
7
- {
8
- description: `From ${originLabel}`,
9
- },
19
+ supportsComponentsV2: true,
20
+ buildCrossContextComponents: ({ originLabel, message, cfg, accountId }) => [
21
+ new CrossContextContainer({ originLabel, message, cfg, accountId }),
10
22
  ],
11
23
  };
12
24
  export function getChannelMessageAdapter(channel) {
13
- if (channel === "discord")
25
+ if (channel === "discord") {
14
26
  return DISCORD_ADAPTER;
27
+ }
15
28
  return DEFAULT_ADAPTER;
16
29
  }