@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.
Files changed (191) hide show
  1. package/CHANGELOG.md +21 -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/sessions.test-helpers.js +61 -0
  63. package/dist/config/commands.js +3 -0
  64. package/dist/config/config.js +1 -1
  65. package/dist/config/env-substitution.js +62 -34
  66. package/dist/config/env-vars.js +9 -0
  67. package/dist/config/io.js +571 -171
  68. package/dist/config/merge-patch.js +50 -4
  69. package/dist/config/redact-snapshot.js +404 -76
  70. package/dist/config/schema.js +58 -570
  71. package/dist/config/validation.js +140 -85
  72. package/dist/config/zod-schema.hooks.js +40 -11
  73. package/dist/config/zod-schema.installs.js +20 -0
  74. package/dist/config/zod-schema.js +8 -7
  75. package/dist/daemon/cmd-argv.js +21 -0
  76. package/dist/daemon/cmd-set.js +58 -0
  77. package/dist/daemon/service-types.js +1 -0
  78. package/dist/discord/monitor/exec-approvals.js +357 -162
  79. package/dist/gateway/auth.js +38 -3
  80. package/dist/gateway/call.js +149 -68
  81. package/dist/gateway/canvas-capability.js +75 -0
  82. package/dist/gateway/control-plane-audit.js +28 -0
  83. package/dist/gateway/control-plane-rate-limit.js +53 -0
  84. package/dist/gateway/events.js +1 -0
  85. package/dist/gateway/hooks.js +109 -54
  86. package/dist/gateway/http-common.js +22 -0
  87. package/dist/gateway/method-scopes.js +169 -0
  88. package/dist/gateway/net.js +23 -0
  89. package/dist/gateway/openresponses-http.js +120 -110
  90. package/dist/gateway/probe-auth.js +2 -0
  91. package/dist/gateway/protocol/index.js +3 -2
  92. package/dist/gateway/protocol/schema/protocol-schemas.js +2 -0
  93. package/dist/gateway/protocol/schema/push.js +18 -0
  94. package/dist/gateway/protocol/schema.js +1 -0
  95. package/dist/gateway/server-http.js +236 -52
  96. package/dist/gateway/server-methods/agent.js +162 -24
  97. package/dist/gateway/server-methods/chat.js +461 -130
  98. package/dist/gateway/server-methods/config.js +193 -150
  99. package/dist/gateway/server-methods/nodes.helpers.js +12 -0
  100. package/dist/gateway/server-methods/nodes.js +251 -69
  101. package/dist/gateway/server-methods/push.js +53 -0
  102. package/dist/gateway/server-reload-handlers.js +2 -3
  103. package/dist/gateway/server-runtime-config.js +5 -0
  104. package/dist/gateway/server-runtime-state.js +2 -0
  105. package/dist/gateway/server-ws-runtime.js +1 -0
  106. package/dist/gateway/server.impl.js +296 -139
  107. package/dist/gateway/session-preview.test-helpers.js +11 -0
  108. package/dist/gateway/startup-auth.js +126 -0
  109. package/dist/gateway/test-helpers.agent-results.js +15 -0
  110. package/dist/gateway/test-helpers.mocks.js +37 -14
  111. package/dist/gateway/test-helpers.server.js +161 -77
  112. package/dist/hooks/bundled/session-memory/handler.js +165 -34
  113. package/dist/hooks/gmail-watcher-lifecycle.js +23 -0
  114. package/dist/infra/archive-path.js +49 -0
  115. package/dist/infra/device-pairing.js +148 -167
  116. package/dist/infra/exec-approvals-allowlist.js +19 -70
  117. package/dist/infra/exec-approvals-analysis.js +44 -17
  118. package/dist/infra/exec-safe-bin-policy.js +269 -0
  119. package/dist/infra/fixed-window-rate-limit.js +33 -0
  120. package/dist/infra/git-root.js +61 -0
  121. package/dist/infra/heartbeat-active-hours.js +2 -2
  122. package/dist/infra/heartbeat-reason.js +40 -0
  123. package/dist/infra/heartbeat-runner.js +72 -32
  124. package/dist/infra/install-source-utils.js +91 -7
  125. package/dist/infra/node-pairing.js +50 -105
  126. package/dist/infra/npm-integrity.js +45 -0
  127. package/dist/infra/npm-pack-install.js +40 -0
  128. package/dist/infra/outbound/channel-adapters.js +20 -7
  129. package/dist/infra/outbound/message-action-runner.js +107 -327
  130. package/dist/infra/outbound/message.js +59 -36
  131. package/dist/infra/outbound/outbound-policy.js +52 -25
  132. package/dist/infra/outbound/outbound-send-service.js +58 -71
  133. package/dist/infra/pairing-files.js +10 -0
  134. package/dist/infra/plain-object.js +9 -0
  135. package/dist/infra/push-apns.js +365 -0
  136. package/dist/infra/restart-sentinel.js +16 -1
  137. package/dist/infra/restart.js +229 -26
  138. package/dist/infra/scp-host.js +54 -0
  139. package/dist/infra/update-startup.js +86 -9
  140. package/dist/media/inbound-path-policy.js +114 -0
  141. package/dist/media/input-files.js +16 -0
  142. package/dist/memory/test-manager.js +8 -0
  143. package/dist/plugin-sdk/temp-path.js +47 -0
  144. package/dist/plugins/discovery.js +217 -23
  145. package/dist/plugins/hook-runner-global.js +16 -0
  146. package/dist/plugins/loader.js +192 -26
  147. package/dist/plugins/logger.js +8 -0
  148. package/dist/plugins/manifest-registry.js +3 -0
  149. package/dist/plugins/path-safety.js +34 -0
  150. package/dist/plugins/registry.js +5 -2
  151. package/dist/plugins/runtime/index.js +271 -206
  152. package/dist/providers/github-copilot-models.js +4 -1
  153. package/dist/security/audit-channel.js +8 -19
  154. package/dist/security/audit-extra.async.js +354 -182
  155. package/dist/security/audit-extra.js +11 -1
  156. package/dist/security/audit-extra.sync.js +340 -33
  157. package/dist/security/audit-fs.js +31 -13
  158. package/dist/security/audit.js +145 -371
  159. package/dist/security/dm-policy-shared.js +24 -0
  160. package/dist/security/external-content.js +20 -8
  161. package/dist/security/fix.js +49 -85
  162. package/dist/security/scan-paths.js +20 -0
  163. package/dist/security/secret-equal.js +3 -7
  164. package/dist/security/windows-acl.js +30 -15
  165. package/dist/shared/node-list-parse.js +13 -0
  166. package/dist/shared/operator-scope-compat.js +37 -0
  167. package/dist/shared/text-chunking.js +29 -0
  168. package/dist/slack/blocks.test-helpers.js +31 -0
  169. package/dist/slack/monitor/mrkdwn.js +8 -0
  170. package/dist/telegram/bot-message-dispatch.js +366 -164
  171. package/dist/telegram/draft-stream.js +30 -7
  172. package/dist/telegram/reasoning-lane-coordinator.js +128 -0
  173. package/dist/terminal/prompt-select-styled.js +9 -0
  174. package/dist/test-utils/command-runner.js +6 -0
  175. package/dist/test-utils/internal-hook-event-payload.js +10 -0
  176. package/dist/test-utils/model-auth-mock.js +12 -0
  177. package/dist/test-utils/provider-usage-fetch.js +14 -0
  178. package/dist/test-utils/temp-home.js +33 -0
  179. package/dist/tui/components/chat-log.js +9 -0
  180. package/dist/tui/tui-command-handlers.js +36 -27
  181. package/dist/tui/tui-event-handlers.js +122 -32
  182. package/dist/tui/tui.js +181 -45
  183. package/dist/utils/mask-api-key.js +10 -0
  184. package/dist/utils/run-with-concurrency.js +39 -0
  185. package/dist/web/media.js +4 -0
  186. package/docs/tools/slash-commands.md +5 -1
  187. package/extensions/feishu/src/external-keys.ts +19 -0
  188. package/extensions/lobster/src/windows-spawn.ts +193 -0
  189. package/extensions/matrix/src/matrix/actions/limits.ts +6 -0
  190. package/extensions/mattermost/src/mattermost/reactions.test-helpers.ts +83 -0
  191. package/package.json +1 -1
@@ -1,26 +1,14 @@
1
1
  import fs from "node:fs/promises";
2
2
  import JSON5 from "json5";
3
3
  import { readExecApprovalsSnapshot, saveExecApprovals, } from "../infra/exec-approvals.js";
4
+ import { formatTimeAgo } from "../infra/format-time/format-relative.js";
4
5
  import { defaultRuntime } from "../runtime.js";
5
6
  import { formatDocsLink } from "../terminal/links.js";
6
- import { isRich, theme } from "../terminal/theme.js";
7
7
  import { renderTable } from "../terminal/table.js";
8
- import { callGatewayFromCli } from "./gateway-rpc.js";
8
+ import { isRich, theme } from "../terminal/theme.js";
9
9
  import { describeUnknownError } from "./gateway-cli/shared.js";
10
+ import { callGatewayFromCli } from "./gateway-rpc.js";
10
11
  import { nodesCallOpts, resolveNodeId } from "./nodes-cli/rpc.js";
11
- function formatAge(msAgo) {
12
- const s = Math.max(0, Math.floor(msAgo / 1000));
13
- if (s < 60)
14
- return `${s}s`;
15
- const m = Math.floor(s / 60);
16
- if (m < 60)
17
- return `${m}m`;
18
- const h = Math.floor(m / 60);
19
- if (h < 24)
20
- return `${h}h`;
21
- const d = Math.floor(h / 24);
22
- return `${d}d`;
23
- }
24
12
  async function readStdin() {
25
13
  const chunks = [];
26
14
  for await (const chunk of process.stdin) {
@@ -29,11 +17,13 @@ async function readStdin() {
29
17
  return Buffer.concat(chunks).toString("utf8");
30
18
  }
31
19
  async function resolveTargetNodeId(opts) {
32
- if (opts.gateway)
20
+ if (opts.gateway) {
33
21
  return null;
22
+ }
34
23
  const raw = opts.node?.trim() ?? "";
35
- if (!raw)
24
+ if (!raw) {
36
25
  return null;
26
+ }
37
27
  return await resolveNodeId(opts, raw);
38
28
  }
39
29
  async function loadSnapshot(opts, nodeId) {
@@ -63,6 +53,41 @@ async function loadSnapshotTarget(opts) {
63
53
  const snapshot = await loadSnapshot(opts, nodeId);
64
54
  return { snapshot, nodeId, source: nodeId ? "node" : "gateway" };
65
55
  }
56
+ function exitWithError(message) {
57
+ defaultRuntime.error(message);
58
+ defaultRuntime.exit(1);
59
+ throw new Error(message);
60
+ }
61
+ function requireTrimmedNonEmpty(value, message) {
62
+ const trimmed = value.trim();
63
+ if (!trimmed) {
64
+ exitWithError(message);
65
+ }
66
+ return trimmed;
67
+ }
68
+ async function loadWritableSnapshotTarget(opts) {
69
+ const { snapshot, nodeId, source } = await loadSnapshotTarget(opts);
70
+ if (source === "local") {
71
+ defaultRuntime.log(theme.muted("Writing local approvals."));
72
+ }
73
+ const targetLabel = source === "local" ? "local" : nodeId ? `node:${nodeId}` : "gateway";
74
+ const baseHash = snapshot.hash;
75
+ if (!baseHash) {
76
+ exitWithError("Exec approvals hash missing; reload and retry.");
77
+ }
78
+ return { snapshot, nodeId, source, targetLabel, baseHash };
79
+ }
80
+ async function saveSnapshotTargeted(params) {
81
+ const next = params.source === "local"
82
+ ? saveSnapshotLocal(params.file)
83
+ : await saveSnapshot(params.opts, params.nodeId, params.file, params.baseHash);
84
+ if (params.opts.json) {
85
+ defaultRuntime.log(JSON.stringify(next));
86
+ return;
87
+ }
88
+ defaultRuntime.log(theme.muted(`Target: ${params.targetLabel}`));
89
+ renderApprovalsSnapshot(next, params.targetLabel);
90
+ }
66
91
  function formatCliError(err) {
67
92
  const msg = describeUnknownError(err);
68
93
  return msg.includes("\n") ? msg.split("\n")[0] : msg;
@@ -89,14 +114,15 @@ function renderApprovalsSnapshot(snapshot, targetLabel) {
89
114
  const allowlist = Array.isArray(agent.allowlist) ? agent.allowlist : [];
90
115
  for (const entry of allowlist) {
91
116
  const pattern = entry?.pattern?.trim() ?? "";
92
- if (!pattern)
117
+ if (!pattern) {
93
118
  continue;
119
+ }
94
120
  const lastUsedAt = typeof entry.lastUsedAt === "number" ? entry.lastUsedAt : null;
95
121
  allowlistRows.push({
96
122
  Target: targetLabel,
97
123
  Agent: agentId,
98
124
  Pattern: pattern,
99
- LastUsed: lastUsedAt ? `${formatAge(Math.max(0, now - lastUsedAt))} ago` : muted("unknown"),
125
+ LastUsed: lastUsedAt ? formatTimeAgo(Math.max(0, now - lastUsedAt)) : muted("unknown"),
100
126
  });
101
127
  }
102
128
  }
@@ -166,6 +192,37 @@ function isEmptyAgent(agent) {
166
192
  agent.autoAllowSkills === undefined &&
167
193
  allowlist.length === 0);
168
194
  }
195
+ async function loadWritableAllowlistAgent(opts) {
196
+ const { snapshot, nodeId, source, targetLabel, baseHash } = await loadWritableSnapshotTarget(opts);
197
+ const file = snapshot.file ?? { version: 1 };
198
+ file.version = 1;
199
+ const agentKey = resolveAgentKey(opts.agent);
200
+ const agent = ensureAgent(file, agentKey);
201
+ const allowlistEntries = Array.isArray(agent.allowlist) ? agent.allowlist : [];
202
+ return { nodeId, source, targetLabel, baseHash, file, agentKey, agent, allowlistEntries };
203
+ }
204
+ async function runAllowlistMutation(pattern, opts, mutate) {
205
+ try {
206
+ const trimmedPattern = requireTrimmedNonEmpty(pattern, "Pattern required.");
207
+ const context = await loadWritableAllowlistAgent(opts);
208
+ const shouldSave = await mutate({ ...context, trimmedPattern });
209
+ if (!shouldSave) {
210
+ return;
211
+ }
212
+ await saveSnapshotTargeted({
213
+ opts,
214
+ source: context.source,
215
+ nodeId: context.nodeId,
216
+ file: context.file,
217
+ baseHash: context.baseHash,
218
+ targetLabel: context.targetLabel,
219
+ });
220
+ }
221
+ catch (err) {
222
+ defaultRuntime.error(formatCliError(err));
223
+ defaultRuntime.exit(1);
224
+ }
225
+ }
169
226
  export function registerExecApprovalsCli(program) {
170
227
  const formatExample = (cmd, desc) => ` ${theme.command(cmd)}\n ${theme.muted(desc)}`;
171
228
  const approvals = program
@@ -209,45 +266,22 @@ export function registerExecApprovalsCli(program) {
209
266
  .action(async (opts) => {
210
267
  try {
211
268
  if (!opts.file && !opts.stdin) {
212
- defaultRuntime.error("Provide --file or --stdin.");
213
- defaultRuntime.exit(1);
214
- return;
269
+ exitWithError("Provide --file or --stdin.");
215
270
  }
216
271
  if (opts.file && opts.stdin) {
217
- defaultRuntime.error("Use either --file or --stdin (not both).");
218
- defaultRuntime.exit(1);
219
- return;
220
- }
221
- const { snapshot, nodeId, source } = await loadSnapshotTarget(opts);
222
- if (source === "local") {
223
- defaultRuntime.log(theme.muted("Writing local approvals."));
224
- }
225
- const targetLabel = source === "local" ? "local" : nodeId ? `node:${nodeId}` : "gateway";
226
- if (!snapshot.hash) {
227
- defaultRuntime.error("Exec approvals hash missing; reload and retry.");
228
- defaultRuntime.exit(1);
229
- return;
272
+ exitWithError("Use either --file or --stdin (not both).");
230
273
  }
274
+ const { source, nodeId, targetLabel, baseHash } = await loadWritableSnapshotTarget(opts);
231
275
  const raw = opts.stdin ? await readStdin() : await fs.readFile(String(opts.file), "utf8");
232
276
  let file;
233
277
  try {
234
278
  file = JSON5.parse(raw);
235
279
  }
236
280
  catch (err) {
237
- defaultRuntime.error(`Failed to parse approvals JSON: ${String(err)}`);
238
- defaultRuntime.exit(1);
239
- return;
281
+ exitWithError(`Failed to parse approvals JSON: ${String(err)}`);
240
282
  }
241
283
  file.version = 1;
242
- const next = source === "local"
243
- ? saveSnapshotLocal(file)
244
- : await saveSnapshot(opts, nodeId, file, snapshot.hash);
245
- if (opts.json) {
246
- defaultRuntime.log(JSON.stringify(next));
247
- return;
248
- }
249
- defaultRuntime.log(theme.muted(`Target: ${targetLabel}`));
250
- renderApprovalsSnapshot(next, targetLabel);
284
+ await saveSnapshotTargeted({ opts, source, nodeId, file, baseHash, targetLabel });
251
285
  }
252
286
  catch (err) {
253
287
  defaultRuntime.error(formatCliError(err));
@@ -266,49 +300,16 @@ export function registerExecApprovalsCli(program) {
266
300
  .option("--gateway", "Force gateway approvals", false)
267
301
  .option("--agent <id>", 'Agent id (defaults to "*")')
268
302
  .action(async (pattern, opts) => {
269
- try {
270
- const trimmed = pattern.trim();
271
- if (!trimmed) {
272
- defaultRuntime.error("Pattern required.");
273
- defaultRuntime.exit(1);
274
- return;
275
- }
276
- const { snapshot, nodeId, source } = await loadSnapshotTarget(opts);
277
- if (source === "local") {
278
- defaultRuntime.log(theme.muted("Writing local approvals."));
279
- }
280
- const targetLabel = source === "local" ? "local" : nodeId ? `node:${nodeId}` : "gateway";
281
- if (!snapshot.hash) {
282
- defaultRuntime.error("Exec approvals hash missing; reload and retry.");
283
- defaultRuntime.exit(1);
284
- return;
285
- }
286
- const file = snapshot.file ?? { version: 1 };
287
- file.version = 1;
288
- const agentKey = resolveAgentKey(opts.agent);
289
- const agent = ensureAgent(file, agentKey);
290
- const allowlistEntries = Array.isArray(agent.allowlist) ? agent.allowlist : [];
291
- if (allowlistEntries.some((entry) => normalizeAllowlistEntry(entry) === trimmed)) {
303
+ await runAllowlistMutation(pattern, opts, ({ trimmedPattern, file, agent, agentKey, allowlistEntries }) => {
304
+ if (allowlistEntries.some((entry) => normalizeAllowlistEntry(entry) === trimmedPattern)) {
292
305
  defaultRuntime.log("Already allowlisted.");
293
- return;
306
+ return false;
294
307
  }
295
- allowlistEntries.push({ pattern: trimmed, lastUsedAt: Date.now() });
308
+ allowlistEntries.push({ pattern: trimmedPattern, lastUsedAt: Date.now() });
296
309
  agent.allowlist = allowlistEntries;
297
310
  file.agents = { ...file.agents, [agentKey]: agent };
298
- const next = source === "local"
299
- ? saveSnapshotLocal(file)
300
- : await saveSnapshot(opts, nodeId, file, snapshot.hash);
301
- if (opts.json) {
302
- defaultRuntime.log(JSON.stringify(next));
303
- return;
304
- }
305
- defaultRuntime.log(theme.muted(`Target: ${targetLabel}`));
306
- renderApprovalsSnapshot(next, targetLabel);
307
- }
308
- catch (err) {
309
- defaultRuntime.error(formatCliError(err));
310
- defaultRuntime.exit(1);
311
- }
311
+ return true;
312
+ });
312
313
  });
313
314
  nodesCallOpts(allowlistAdd);
314
315
  const allowlistRemove = allowlist
@@ -318,32 +319,11 @@ export function registerExecApprovalsCli(program) {
318
319
  .option("--gateway", "Force gateway approvals", false)
319
320
  .option("--agent <id>", 'Agent id (defaults to "*")')
320
321
  .action(async (pattern, opts) => {
321
- try {
322
- const trimmed = pattern.trim();
323
- if (!trimmed) {
324
- defaultRuntime.error("Pattern required.");
325
- defaultRuntime.exit(1);
326
- return;
327
- }
328
- const { snapshot, nodeId, source } = await loadSnapshotTarget(opts);
329
- if (source === "local") {
330
- defaultRuntime.log(theme.muted("Writing local approvals."));
331
- }
332
- const targetLabel = source === "local" ? "local" : nodeId ? `node:${nodeId}` : "gateway";
333
- if (!snapshot.hash) {
334
- defaultRuntime.error("Exec approvals hash missing; reload and retry.");
335
- defaultRuntime.exit(1);
336
- return;
337
- }
338
- const file = snapshot.file ?? { version: 1 };
339
- file.version = 1;
340
- const agentKey = resolveAgentKey(opts.agent);
341
- const agent = ensureAgent(file, agentKey);
342
- const allowlistEntries = Array.isArray(agent.allowlist) ? agent.allowlist : [];
343
- const nextEntries = allowlistEntries.filter((entry) => normalizeAllowlistEntry(entry) !== trimmed);
322
+ await runAllowlistMutation(pattern, opts, ({ trimmedPattern, file, agent, agentKey, allowlistEntries }) => {
323
+ const nextEntries = allowlistEntries.filter((entry) => normalizeAllowlistEntry(entry) !== trimmedPattern);
344
324
  if (nextEntries.length === allowlistEntries.length) {
345
325
  defaultRuntime.log("Pattern not found.");
346
- return;
326
+ return false;
347
327
  }
348
328
  if (nextEntries.length === 0) {
349
329
  delete agent.allowlist;
@@ -359,20 +339,8 @@ export function registerExecApprovalsCli(program) {
359
339
  else {
360
340
  file.agents = { ...file.agents, [agentKey]: agent };
361
341
  }
362
- const next = source === "local"
363
- ? saveSnapshotLocal(file)
364
- : await saveSnapshot(opts, nodeId, file, snapshot.hash);
365
- if (opts.json) {
366
- defaultRuntime.log(JSON.stringify(next));
367
- return;
368
- }
369
- defaultRuntime.log(theme.muted(`Target: ${targetLabel}`));
370
- renderApprovalsSnapshot(next, targetLabel);
371
- }
372
- catch (err) {
373
- defaultRuntime.error(formatCliError(err));
374
- defaultRuntime.exit(1);
375
- }
342
+ return true;
343
+ });
376
344
  });
377
345
  nodesCallOpts(allowlistRemove);
378
346
  }