@poolzin/pool-bot 2026.2.0 → 2026.2.1
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/dist/agents/bash-tools.exec.js +76 -25
- package/dist/agents/cli-runner/helpers.js +9 -11
- package/dist/agents/identity.js +47 -7
- package/dist/agents/memory-search.js +25 -8
- package/dist/agents/model-selection.js +21 -0
- package/dist/agents/pi-embedded-block-chunker.js +117 -42
- package/dist/agents/pi-embedded-helpers/errors.js +183 -78
- package/dist/agents/pi-embedded-helpers.js +1 -1
- package/dist/agents/pi-embedded-runner/compact.js +1 -0
- package/dist/agents/pi-embedded-runner/model.js +61 -2
- package/dist/agents/pi-embedded-runner/run/attempt.js +21 -11
- package/dist/agents/pi-embedded-runner/run.js +199 -46
- package/dist/agents/pi-embedded-runner/system-prompt.js +10 -2
- package/dist/agents/pi-embedded-subscribe.js +118 -29
- package/dist/agents/pi-tools.js +10 -5
- package/dist/agents/poolbot-tools.js +15 -10
- package/dist/agents/sandbox-paths.js +31 -0
- package/dist/agents/session-tool-result-guard.js +94 -15
- package/dist/agents/shell-utils.js +51 -0
- package/dist/agents/skills/bundled-context.js +23 -0
- package/dist/agents/skills/bundled-dir.js +41 -7
- package/dist/agents/skills-install.js +60 -23
- package/dist/agents/subagent-announce.js +79 -34
- package/dist/agents/tool-policy.conformance.js +14 -0
- package/dist/agents/tool-policy.js +24 -0
- package/dist/agents/tools/cron-tool.js +166 -19
- package/dist/agents/tools/discord-actions-presence.js +78 -0
- package/dist/agents/tools/message-tool.js +56 -2
- package/dist/agents/tools/sessions-history-tool.js +69 -1
- package/dist/agents/tools/web-search.js +211 -42
- package/dist/agents/usage.js +23 -1
- package/dist/agents/workspace-run.js +67 -0
- package/dist/agents/workspace-templates.js +44 -0
- package/dist/auto-reply/command-auth.js +121 -6
- package/dist/auto-reply/envelope.js +50 -72
- package/dist/auto-reply/reply/commands-compact.js +1 -0
- package/dist/auto-reply/reply/commands-context-report.js +1 -0
- package/dist/auto-reply/reply/commands-context.js +1 -0
- package/dist/auto-reply/reply/commands-models.js +107 -60
- package/dist/auto-reply/reply/commands-ptt.js +171 -0
- package/dist/auto-reply/reply/get-reply-run.js +2 -1
- package/dist/auto-reply/reply/inbound-context.js +5 -1
- package/dist/auto-reply/reply/model-selection.js +3 -3
- package/dist/auto-reply/thinking.js +88 -43
- package/dist/browser/bridge-server.js +13 -0
- package/dist/browser/cdp.helpers.js +38 -24
- package/dist/browser/client-fetch.js +50 -7
- package/dist/browser/config.js +1 -10
- package/dist/browser/extension-relay.js +101 -40
- package/dist/browser/pw-ai.js +1 -1
- package/dist/browser/pw-session.js +143 -8
- package/dist/browser/pw-tools-core.interactions.js +125 -27
- package/dist/browser/pw-tools-core.responses.js +1 -1
- package/dist/browser/pw-tools-core.state.js +1 -1
- package/dist/browser/routes/agent.act.js +86 -41
- package/dist/browser/routes/dispatcher.js +4 -4
- package/dist/browser/screenshot.js +1 -1
- package/dist/browser/server.js +13 -0
- package/dist/build-info.json +3 -3
- package/dist/channels/reply-prefix.js +8 -1
- package/dist/cli/cron-cli/register.cron-add.js +61 -40
- package/dist/cli/cron-cli/register.cron-edit.js +60 -34
- package/dist/cli/cron-cli/shared.js +56 -41
- package/dist/cli/dns-cli.js +26 -14
- package/dist/cli/gateway-cli/register.js +37 -19
- package/dist/cli/memory-cli.js +5 -5
- package/dist/cli/parse-bytes.js +37 -0
- package/dist/cli/update-cli.js +173 -52
- package/dist/commands/agent.js +1 -0
- package/dist/commands/doctor-config-flow.js +61 -5
- package/dist/commands/doctor-state-migrations.js +1 -1
- package/dist/commands/health.js +1 -1
- package/dist/commands/model-allowlist.js +29 -0
- package/dist/commands/model-picker.js +2 -1
- package/dist/commands/models/list.status-command.js +43 -23
- package/dist/commands/models/shared.js +15 -0
- package/dist/commands/onboard-custom.js +384 -0
- package/dist/commands/onboard-non-interactive/local/auth-choice-inference.js +35 -0
- package/dist/commands/onboard-non-interactive/local/auth-choice.js +6 -3
- package/dist/commands/onboard-skills.js +63 -38
- package/dist/commands/openai-model-default.js +41 -0
- package/dist/config/defaults.js +3 -2
- package/dist/config/paths.js +136 -35
- package/dist/config/plugin-auto-enable.js +21 -5
- package/dist/config/redact-snapshot.js +153 -0
- package/dist/config/schema.field-metadata.js +590 -0
- package/dist/config/schema.js +2 -2
- package/dist/config/sessions/store.js +291 -23
- package/dist/config/zod-schema.agent-defaults.js +3 -0
- package/dist/config/zod-schema.agent-runtime.js +13 -2
- package/dist/config/zod-schema.providers-core.js +142 -0
- package/dist/config/zod-schema.session.js +3 -0
- package/dist/cron/delivery.js +57 -0
- package/dist/cron/isolated-agent/delivery-target.js +18 -3
- package/dist/cron/isolated-agent/helpers.js +22 -5
- package/dist/cron/isolated-agent/run.js +171 -63
- package/dist/cron/isolated-agent/session.js +2 -0
- package/dist/cron/normalize.js +356 -28
- package/dist/cron/parse.js +10 -5
- package/dist/cron/run-log.js +35 -10
- package/dist/cron/schedule.js +41 -6
- package/dist/cron/service/jobs.js +208 -35
- package/dist/cron/service/ops.js +72 -16
- package/dist/cron/service/state.js +2 -0
- package/dist/cron/service/store.js +386 -14
- package/dist/cron/service/timer.js +390 -147
- package/dist/cron/session-reaper.js +86 -0
- package/dist/cron/store.js +23 -8
- package/dist/cron/validate-timestamp.js +43 -0
- package/dist/discord/monitor/agent-components.js +438 -0
- package/dist/discord/monitor/allow-list.js +28 -5
- package/dist/discord/monitor/gateway-registry.js +29 -0
- package/dist/discord/monitor/native-command.js +44 -23
- package/dist/discord/monitor/sender-identity.js +45 -0
- package/dist/discord/pluralkit.js +27 -0
- package/dist/discord/send.outbound.js +92 -5
- package/dist/discord/send.shared.js +60 -23
- package/dist/discord/targets.js +84 -1
- package/dist/entry.js +15 -9
- package/dist/extensionAPI.js +8 -0
- package/dist/gateway/control-ui.js +8 -1
- package/dist/gateway/hooks-mapping.js +3 -0
- package/dist/gateway/hooks.js +65 -0
- package/dist/gateway/net.js +96 -31
- package/dist/gateway/node-command-policy.js +50 -15
- package/dist/gateway/origin-check.js +56 -0
- package/dist/gateway/protocol/client-info.js +9 -0
- package/dist/gateway/protocol/index.js +9 -2
- package/dist/gateway/protocol/schema/agents-models-skills.js +71 -1
- package/dist/gateway/protocol/schema/cron.js +22 -10
- package/dist/gateway/protocol/schema/protocol-schemas.js +16 -2
- package/dist/gateway/protocol/schema/sessions.js +12 -0
- package/dist/gateway/server/hooks.js +1 -1
- package/dist/gateway/server-broadcast.js +26 -9
- package/dist/gateway/server-chat.js +112 -23
- package/dist/gateway/server-discovery-runtime.js +10 -2
- package/dist/gateway/server-http.js +109 -11
- package/dist/gateway/server-methods/agent-timestamp.js +60 -0
- package/dist/gateway/server-methods/agents.js +321 -2
- package/dist/gateway/server-methods/usage.js +559 -16
- package/dist/gateway/server-runtime-state.js +22 -8
- package/dist/gateway/server-startup-memory.js +16 -0
- package/dist/gateway/server.impl.js +5 -1
- package/dist/gateway/session-utils.fs.js +23 -25
- package/dist/gateway/session-utils.js +20 -10
- package/dist/gateway/sessions-patch.js +7 -22
- package/dist/gateway/test-helpers.server.js +35 -2
- package/dist/imessage/constants.js +2 -0
- package/dist/imessage/monitor/deliver.js +4 -1
- package/dist/imessage/monitor/monitor-provider.js +51 -1
- package/dist/infra/bonjour-discovery.js +131 -70
- package/dist/infra/control-ui-assets.js +134 -12
- package/dist/infra/errors.js +12 -0
- package/dist/infra/exec-approvals.js +266 -57
- package/dist/infra/format-time/format-datetime.js +79 -0
- package/dist/infra/format-time/format-duration.js +81 -0
- package/dist/infra/format-time/format-relative.js +80 -0
- package/dist/infra/heartbeat-runner.js +140 -49
- package/dist/infra/home-dir.js +54 -0
- package/dist/infra/net/fetch-guard.js +122 -0
- package/dist/infra/net/ssrf.js +65 -29
- package/dist/infra/outbound/abort.js +14 -0
- package/dist/infra/outbound/message-action-runner.js +77 -13
- package/dist/infra/outbound/outbound-session.js +143 -37
- package/dist/infra/poolbot-root.js +43 -1
- package/dist/infra/session-cost-usage.js +631 -41
- package/dist/infra/state-migrations.js +317 -47
- package/dist/infra/update-global.js +35 -0
- package/dist/infra/update-runner.js +149 -43
- package/dist/infra/warning-filter.js +65 -0
- package/dist/infra/widearea-dns.js +30 -9
- package/dist/logging/redact-identifier.js +12 -0
- package/dist/media/fetch.js +81 -58
- package/dist/media-understanding/apply.js +403 -3
- package/dist/media-understanding/attachments.js +38 -27
- package/dist/media-understanding/defaults.js +16 -0
- package/dist/media-understanding/providers/deepgram/audio.js +22 -14
- package/dist/media-understanding/providers/google/audio.js +24 -17
- package/dist/media-understanding/providers/google/video.js +24 -17
- package/dist/media-understanding/providers/image.js +2 -2
- package/dist/media-understanding/providers/index.js +4 -1
- package/dist/media-understanding/providers/openai/audio.js +22 -14
- package/dist/media-understanding/providers/shared.js +16 -11
- package/dist/media-understanding/providers/zai/index.js +6 -0
- package/dist/media-understanding/runner.js +158 -90
- package/dist/memory/batch-voyage.js +277 -0
- package/dist/memory/embeddings-voyage.js +75 -0
- package/dist/memory/embeddings.js +28 -16
- package/dist/memory/internal.js +101 -18
- package/dist/memory/manager.js +154 -48
- package/dist/memory/search-manager.js +173 -0
- package/dist/memory/session-files.js +9 -3
- package/dist/node-host/runner.js +34 -24
- package/dist/node-host/with-timeout.js +27 -0
- package/dist/plugins/commands.js +5 -1
- package/dist/plugins/config-state.js +86 -7
- package/dist/plugins/source-display.js +51 -0
- package/dist/process/exec.js +20 -2
- package/dist/routing/resolve-route.js +12 -0
- package/dist/routing/session-key.js +15 -0
- package/dist/runtime.js +2 -0
- package/dist/security/audit-extra.async.js +601 -0
- package/dist/security/audit-extra.js +2 -830
- package/dist/security/audit-extra.sync.js +505 -0
- package/dist/security/channel-metadata.js +34 -0
- package/dist/security/external-content.js +88 -6
- package/dist/security/skill-scanner.js +330 -0
- package/dist/sessions/session-key-utils.js +7 -0
- package/dist/signal/monitor/event-handler.js +80 -1
- package/dist/slack/monitor/media.js +85 -15
- package/dist/tailscale/detect.js +1 -2
- package/dist/telegram/bot/helpers.js +109 -28
- package/dist/telegram/bot-handlers.js +144 -3
- package/dist/telegram/bot-message-context.js +37 -10
- package/dist/telegram/bot-message-dispatch.js +48 -15
- package/dist/telegram/bot-native-commands.js +86 -29
- package/dist/telegram/bot.js +30 -29
- package/dist/telegram/model-buttons.js +163 -0
- package/dist/telegram/monitor.js +110 -85
- package/dist/telegram/send.js +129 -47
- package/dist/terminal/restore.js +45 -0
- package/dist/test-helpers/state-dir-env.js +16 -0
- package/dist/tts/tts.js +12 -6
- package/dist/tui/tui-session-actions.js +166 -54
- package/dist/utils/fetch-timeout.js +20 -0
- package/dist/utils/normalize-secret-input.js +19 -0
- package/dist/utils/transcript-tools.js +58 -0
- package/dist/utils.js +45 -14
- package/dist/version.js +42 -5
- package/package.json +1 -1
|
@@ -18,12 +18,15 @@ function hashExecApprovalsRaw(raw) {
|
|
|
18
18
|
.digest("hex");
|
|
19
19
|
}
|
|
20
20
|
function expandHome(value) {
|
|
21
|
-
if (!value)
|
|
21
|
+
if (!value) {
|
|
22
22
|
return value;
|
|
23
|
-
|
|
23
|
+
}
|
|
24
|
+
if (value === "~") {
|
|
24
25
|
return os.homedir();
|
|
25
|
-
|
|
26
|
+
}
|
|
27
|
+
if (value.startsWith("~/")) {
|
|
26
28
|
return path.join(os.homedir(), value.slice(2));
|
|
29
|
+
}
|
|
27
30
|
return value;
|
|
28
31
|
}
|
|
29
32
|
export function resolveExecApprovalsPath() {
|
|
@@ -41,15 +44,18 @@ function mergeLegacyAgent(current, legacy) {
|
|
|
41
44
|
const seen = new Set();
|
|
42
45
|
const pushEntry = (entry) => {
|
|
43
46
|
const key = normalizeAllowlistPattern(entry.pattern);
|
|
44
|
-
if (!key || seen.has(key))
|
|
47
|
+
if (!key || seen.has(key)) {
|
|
45
48
|
return;
|
|
49
|
+
}
|
|
46
50
|
seen.add(key);
|
|
47
51
|
allowlist.push(entry);
|
|
48
52
|
};
|
|
49
|
-
for (const entry of current.allowlist ?? [])
|
|
53
|
+
for (const entry of current.allowlist ?? []) {
|
|
50
54
|
pushEntry(entry);
|
|
51
|
-
|
|
55
|
+
}
|
|
56
|
+
for (const entry of legacy.allowlist ?? []) {
|
|
52
57
|
pushEntry(entry);
|
|
58
|
+
}
|
|
53
59
|
return {
|
|
54
60
|
security: current.security ?? legacy.security,
|
|
55
61
|
ask: current.ask ?? legacy.ask,
|
|
@@ -62,13 +68,49 @@ function ensureDir(filePath) {
|
|
|
62
68
|
const dir = path.dirname(filePath);
|
|
63
69
|
fs.mkdirSync(dir, { recursive: true });
|
|
64
70
|
}
|
|
71
|
+
// Coerce legacy/corrupted allowlists into `ExecAllowlistEntry[]` before we spread
|
|
72
|
+
// entries to add ids (spreading strings creates {"0":"l","1":"s",...}).
|
|
73
|
+
function coerceAllowlistEntries(allowlist) {
|
|
74
|
+
if (!Array.isArray(allowlist) || allowlist.length === 0) {
|
|
75
|
+
return Array.isArray(allowlist) ? allowlist : undefined;
|
|
76
|
+
}
|
|
77
|
+
let changed = false;
|
|
78
|
+
const result = [];
|
|
79
|
+
for (const item of allowlist) {
|
|
80
|
+
if (typeof item === "string") {
|
|
81
|
+
const trimmed = item.trim();
|
|
82
|
+
if (trimmed) {
|
|
83
|
+
result.push({ pattern: trimmed });
|
|
84
|
+
changed = true;
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
changed = true; // dropped empty string
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else if (item && typeof item === "object" && !Array.isArray(item)) {
|
|
91
|
+
const pattern = item.pattern;
|
|
92
|
+
if (typeof pattern === "string" && pattern.trim().length > 0) {
|
|
93
|
+
result.push(item);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
changed = true; // dropped invalid entry
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
changed = true; // dropped invalid entry
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return changed ? (result.length > 0 ? result : undefined) : allowlist;
|
|
104
|
+
}
|
|
65
105
|
function ensureAllowlistIds(allowlist) {
|
|
66
|
-
if (!Array.isArray(allowlist) || allowlist.length === 0)
|
|
106
|
+
if (!Array.isArray(allowlist) || allowlist.length === 0) {
|
|
67
107
|
return allowlist;
|
|
108
|
+
}
|
|
68
109
|
let changed = false;
|
|
69
110
|
const next = allowlist.map((entry) => {
|
|
70
|
-
if (entry.id)
|
|
111
|
+
if (entry.id) {
|
|
71
112
|
return entry;
|
|
113
|
+
}
|
|
72
114
|
changed = true;
|
|
73
115
|
return { ...entry, id: crypto.randomUUID() };
|
|
74
116
|
});
|
|
@@ -85,7 +127,8 @@ export function normalizeExecApprovals(file) {
|
|
|
85
127
|
delete agents.default;
|
|
86
128
|
}
|
|
87
129
|
for (const [key, agent] of Object.entries(agents)) {
|
|
88
|
-
const
|
|
130
|
+
const coerced = coerceAllowlistEntries(agent.allowlist);
|
|
131
|
+
const allowlist = ensureAllowlistIds(coerced);
|
|
89
132
|
if (allowlist !== agent.allowlist) {
|
|
90
133
|
agents[key] = { ...agent, allowlist };
|
|
91
134
|
}
|
|
@@ -184,13 +227,15 @@ export function ensureExecApprovals() {
|
|
|
184
227
|
return updated;
|
|
185
228
|
}
|
|
186
229
|
function normalizeSecurity(value, fallback) {
|
|
187
|
-
if (value === "allowlist" || value === "full" || value === "deny")
|
|
230
|
+
if (value === "allowlist" || value === "full" || value === "deny") {
|
|
188
231
|
return value;
|
|
232
|
+
}
|
|
189
233
|
return fallback;
|
|
190
234
|
}
|
|
191
235
|
function normalizeAsk(value, fallback) {
|
|
192
|
-
if (value === "always" || value === "off" || value === "on-miss")
|
|
236
|
+
if (value === "always" || value === "off" || value === "on-miss") {
|
|
193
237
|
return value;
|
|
238
|
+
}
|
|
194
239
|
return fallback;
|
|
195
240
|
}
|
|
196
241
|
export function resolveExecApprovals(agentId, overrides) {
|
|
@@ -243,8 +288,9 @@ export function resolveExecApprovalsFromFile(params) {
|
|
|
243
288
|
function isExecutableFile(filePath) {
|
|
244
289
|
try {
|
|
245
290
|
const stat = fs.statSync(filePath);
|
|
246
|
-
if (!stat.isFile())
|
|
291
|
+
if (!stat.isFile()) {
|
|
247
292
|
return false;
|
|
293
|
+
}
|
|
248
294
|
if (process.platform !== "win32") {
|
|
249
295
|
fs.accessSync(filePath, fs.constants.X_OK);
|
|
250
296
|
}
|
|
@@ -256,13 +302,15 @@ function isExecutableFile(filePath) {
|
|
|
256
302
|
}
|
|
257
303
|
function parseFirstToken(command) {
|
|
258
304
|
const trimmed = command.trim();
|
|
259
|
-
if (!trimmed)
|
|
305
|
+
if (!trimmed) {
|
|
260
306
|
return null;
|
|
307
|
+
}
|
|
261
308
|
const first = trimmed[0];
|
|
262
309
|
if (first === '"' || first === "'") {
|
|
263
310
|
const end = trimmed.indexOf(first, 1);
|
|
264
|
-
if (end > 1)
|
|
311
|
+
if (end > 1) {
|
|
265
312
|
return trimmed.slice(1, end);
|
|
313
|
+
}
|
|
266
314
|
return trimmed.slice(1);
|
|
267
315
|
}
|
|
268
316
|
const match = /^[^\s]+/.exec(trimmed);
|
|
@@ -295,24 +343,27 @@ function resolveExecutablePath(rawExecutable, cwd, env) {
|
|
|
295
343
|
for (const entry of entries) {
|
|
296
344
|
for (const ext of extensions) {
|
|
297
345
|
const candidate = path.join(entry, expanded + ext);
|
|
298
|
-
if (isExecutableFile(candidate))
|
|
346
|
+
if (isExecutableFile(candidate)) {
|
|
299
347
|
return candidate;
|
|
348
|
+
}
|
|
300
349
|
}
|
|
301
350
|
}
|
|
302
351
|
return undefined;
|
|
303
352
|
}
|
|
304
353
|
export function resolveCommandResolution(command, cwd, env) {
|
|
305
354
|
const rawExecutable = parseFirstToken(command);
|
|
306
|
-
if (!rawExecutable)
|
|
355
|
+
if (!rawExecutable) {
|
|
307
356
|
return null;
|
|
357
|
+
}
|
|
308
358
|
const resolvedPath = resolveExecutablePath(rawExecutable, cwd, env);
|
|
309
359
|
const executableName = resolvedPath ? path.basename(resolvedPath) : rawExecutable;
|
|
310
360
|
return { rawExecutable, resolvedPath, executableName };
|
|
311
361
|
}
|
|
312
362
|
export function resolveCommandResolutionFromArgv(argv, cwd, env) {
|
|
313
363
|
const rawExecutable = argv[0]?.trim();
|
|
314
|
-
if (!rawExecutable)
|
|
364
|
+
if (!rawExecutable) {
|
|
315
365
|
return null;
|
|
366
|
+
}
|
|
316
367
|
const resolvedPath = resolveExecutablePath(rawExecutable, cwd, env);
|
|
317
368
|
const executableName = resolvedPath ? path.basename(resolvedPath) : rawExecutable;
|
|
318
369
|
return { rawExecutable, resolvedPath, executableName };
|
|
@@ -361,8 +412,9 @@ function globToRegExp(pattern) {
|
|
|
361
412
|
}
|
|
362
413
|
function matchesPattern(pattern, target) {
|
|
363
414
|
const trimmed = pattern.trim();
|
|
364
|
-
if (!trimmed)
|
|
415
|
+
if (!trimmed) {
|
|
365
416
|
return false;
|
|
417
|
+
}
|
|
366
418
|
const expanded = trimmed.startsWith("~") ? expandHome(trimmed) : trimmed;
|
|
367
419
|
const hasWildcard = /[*?]/.test(expanded);
|
|
368
420
|
let normalizedPattern = expanded;
|
|
@@ -377,38 +429,64 @@ function matchesPattern(pattern, target) {
|
|
|
377
429
|
return regex.test(normalizedTarget);
|
|
378
430
|
}
|
|
379
431
|
function resolveAllowlistCandidatePath(resolution, cwd) {
|
|
380
|
-
if (!resolution)
|
|
432
|
+
if (!resolution) {
|
|
381
433
|
return undefined;
|
|
382
|
-
|
|
434
|
+
}
|
|
435
|
+
if (resolution.resolvedPath) {
|
|
383
436
|
return resolution.resolvedPath;
|
|
437
|
+
}
|
|
384
438
|
const raw = resolution.rawExecutable?.trim();
|
|
385
|
-
if (!raw)
|
|
439
|
+
if (!raw) {
|
|
386
440
|
return undefined;
|
|
441
|
+
}
|
|
387
442
|
const expanded = raw.startsWith("~") ? expandHome(raw) : raw;
|
|
388
|
-
if (!expanded.includes("/") && !expanded.includes("\\"))
|
|
443
|
+
if (!expanded.includes("/") && !expanded.includes("\\")) {
|
|
389
444
|
return undefined;
|
|
390
|
-
|
|
445
|
+
}
|
|
446
|
+
if (path.isAbsolute(expanded)) {
|
|
391
447
|
return expanded;
|
|
448
|
+
}
|
|
392
449
|
const base = cwd && cwd.trim() ? cwd.trim() : process.cwd();
|
|
393
450
|
return path.resolve(base, expanded);
|
|
394
451
|
}
|
|
395
452
|
export function matchAllowlist(entries, resolution) {
|
|
396
|
-
if (!entries.length || !resolution?.resolvedPath)
|
|
453
|
+
if (!entries.length || !resolution?.resolvedPath) {
|
|
397
454
|
return null;
|
|
455
|
+
}
|
|
398
456
|
const resolvedPath = resolution.resolvedPath;
|
|
399
457
|
for (const entry of entries) {
|
|
400
458
|
const pattern = entry.pattern?.trim();
|
|
401
|
-
if (!pattern)
|
|
459
|
+
if (!pattern) {
|
|
402
460
|
continue;
|
|
461
|
+
}
|
|
403
462
|
const hasPath = pattern.includes("/") || pattern.includes("\\") || pattern.includes("~");
|
|
404
|
-
if (!hasPath)
|
|
463
|
+
if (!hasPath) {
|
|
405
464
|
continue;
|
|
406
|
-
|
|
465
|
+
}
|
|
466
|
+
if (matchesPattern(pattern, resolvedPath)) {
|
|
407
467
|
return entry;
|
|
468
|
+
}
|
|
408
469
|
}
|
|
409
470
|
return null;
|
|
410
471
|
}
|
|
411
472
|
const DISALLOWED_PIPELINE_TOKENS = new Set([">", "<", "`", "\n", "\r", "(", ")"]);
|
|
473
|
+
const DOUBLE_QUOTE_ESCAPES = new Set(["\\", '"', "$", "`", "\n", "\r"]);
|
|
474
|
+
const WINDOWS_UNSUPPORTED_TOKENS = new Set([
|
|
475
|
+
"&",
|
|
476
|
+
"|",
|
|
477
|
+
"<",
|
|
478
|
+
">",
|
|
479
|
+
"^",
|
|
480
|
+
"(",
|
|
481
|
+
")",
|
|
482
|
+
"%",
|
|
483
|
+
"!",
|
|
484
|
+
"\n",
|
|
485
|
+
"\r",
|
|
486
|
+
]);
|
|
487
|
+
function isDoubleQuoteEscape(next) {
|
|
488
|
+
return Boolean(next && DOUBLE_QUOTE_ESCAPES.has(next));
|
|
489
|
+
}
|
|
412
490
|
/**
|
|
413
491
|
* Iterates through a command string while respecting shell quoting rules.
|
|
414
492
|
* The callback receives each character and the next character, and returns an action:
|
|
@@ -445,14 +523,31 @@ function iterateQuoteAware(command, onChar) {
|
|
|
445
523
|
continue;
|
|
446
524
|
}
|
|
447
525
|
if (inSingle) {
|
|
448
|
-
if (ch === "'")
|
|
526
|
+
if (ch === "'") {
|
|
449
527
|
inSingle = false;
|
|
528
|
+
}
|
|
450
529
|
buf += ch;
|
|
451
530
|
continue;
|
|
452
531
|
}
|
|
453
532
|
if (inDouble) {
|
|
454
|
-
if (ch ===
|
|
533
|
+
if (ch === "\\" && isDoubleQuoteEscape(next)) {
|
|
534
|
+
buf += ch;
|
|
535
|
+
buf += next;
|
|
536
|
+
i += 1;
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
if (ch === "$" && next === "(") {
|
|
540
|
+
return { ok: false, reason: "unsupported shell token: $()" };
|
|
541
|
+
}
|
|
542
|
+
if (ch === "`") {
|
|
543
|
+
return { ok: false, reason: "unsupported shell token: `" };
|
|
544
|
+
}
|
|
545
|
+
if (ch === "\n" || ch === "\r") {
|
|
546
|
+
return { ok: false, reason: "unsupported shell token: newline" };
|
|
547
|
+
}
|
|
548
|
+
if (ch === '"') {
|
|
455
549
|
inDouble = false;
|
|
550
|
+
}
|
|
456
551
|
buf += ch;
|
|
457
552
|
continue;
|
|
458
553
|
}
|
|
@@ -523,6 +618,75 @@ function splitShellPipeline(command) {
|
|
|
523
618
|
}
|
|
524
619
|
return { ok: true, segments: result.parts };
|
|
525
620
|
}
|
|
621
|
+
function findWindowsUnsupportedToken(command) {
|
|
622
|
+
for (const ch of command) {
|
|
623
|
+
if (WINDOWS_UNSUPPORTED_TOKENS.has(ch)) {
|
|
624
|
+
if (ch === "\n" || ch === "\r") {
|
|
625
|
+
return "newline";
|
|
626
|
+
}
|
|
627
|
+
return ch;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
return null;
|
|
631
|
+
}
|
|
632
|
+
function tokenizeWindowsSegment(segment) {
|
|
633
|
+
const tokens = [];
|
|
634
|
+
let buf = "";
|
|
635
|
+
let inDouble = false;
|
|
636
|
+
const pushToken = () => {
|
|
637
|
+
if (buf.length > 0) {
|
|
638
|
+
tokens.push(buf);
|
|
639
|
+
buf = "";
|
|
640
|
+
}
|
|
641
|
+
};
|
|
642
|
+
for (let i = 0; i < segment.length; i += 1) {
|
|
643
|
+
const ch = segment[i];
|
|
644
|
+
if (ch === '"') {
|
|
645
|
+
inDouble = !inDouble;
|
|
646
|
+
continue;
|
|
647
|
+
}
|
|
648
|
+
if (!inDouble && /\s/.test(ch)) {
|
|
649
|
+
pushToken();
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
buf += ch;
|
|
653
|
+
}
|
|
654
|
+
if (inDouble) {
|
|
655
|
+
return null;
|
|
656
|
+
}
|
|
657
|
+
pushToken();
|
|
658
|
+
return tokens.length > 0 ? tokens : null;
|
|
659
|
+
}
|
|
660
|
+
function analyzeWindowsShellCommand(params) {
|
|
661
|
+
const unsupported = findWindowsUnsupportedToken(params.command);
|
|
662
|
+
if (unsupported) {
|
|
663
|
+
return {
|
|
664
|
+
ok: false,
|
|
665
|
+
reason: `unsupported windows shell token: ${unsupported}`,
|
|
666
|
+
segments: [],
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
const argv = tokenizeWindowsSegment(params.command);
|
|
670
|
+
if (!argv || argv.length === 0) {
|
|
671
|
+
return { ok: false, reason: "unable to parse windows command", segments: [] };
|
|
672
|
+
}
|
|
673
|
+
return {
|
|
674
|
+
ok: true,
|
|
675
|
+
segments: [
|
|
676
|
+
{
|
|
677
|
+
raw: params.command,
|
|
678
|
+
argv,
|
|
679
|
+
resolution: resolveCommandResolutionFromArgv(argv, params.cwd, params.env),
|
|
680
|
+
},
|
|
681
|
+
],
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
function isWindowsPlatform(platform) {
|
|
685
|
+
const normalized = String(platform ?? "")
|
|
686
|
+
.trim()
|
|
687
|
+
.toLowerCase();
|
|
688
|
+
return normalized.startsWith("win");
|
|
689
|
+
}
|
|
526
690
|
function tokenizeShellSegment(segment) {
|
|
527
691
|
const tokens = [];
|
|
528
692
|
let buf = "";
|
|
@@ -556,6 +720,12 @@ function tokenizeShellSegment(segment) {
|
|
|
556
720
|
continue;
|
|
557
721
|
}
|
|
558
722
|
if (inDouble) {
|
|
723
|
+
const next = segment[i + 1];
|
|
724
|
+
if (ch === "\\" && isDoubleQuoteEscape(next)) {
|
|
725
|
+
buf += next;
|
|
726
|
+
i += 1;
|
|
727
|
+
continue;
|
|
728
|
+
}
|
|
559
729
|
if (ch === '"') {
|
|
560
730
|
inDouble = false;
|
|
561
731
|
}
|
|
@@ -600,6 +770,9 @@ function parseSegmentsFromParts(parts, cwd, env) {
|
|
|
600
770
|
return segments;
|
|
601
771
|
}
|
|
602
772
|
export function analyzeShellCommand(params) {
|
|
773
|
+
if (isWindowsPlatform(params.platform)) {
|
|
774
|
+
return analyzeWindowsShellCommand(params);
|
|
775
|
+
}
|
|
603
776
|
// First try splitting by chain operators (&&, ||, ;)
|
|
604
777
|
const chainParts = splitCommandChain(params.command);
|
|
605
778
|
if (chainParts) {
|
|
@@ -648,14 +821,18 @@ export function analyzeArgvCommand(params) {
|
|
|
648
821
|
}
|
|
649
822
|
function isPathLikeToken(value) {
|
|
650
823
|
const trimmed = value.trim();
|
|
651
|
-
if (!trimmed)
|
|
824
|
+
if (!trimmed) {
|
|
652
825
|
return false;
|
|
653
|
-
|
|
826
|
+
}
|
|
827
|
+
if (trimmed === "-") {
|
|
654
828
|
return false;
|
|
655
|
-
|
|
829
|
+
}
|
|
830
|
+
if (trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed.startsWith("~")) {
|
|
656
831
|
return true;
|
|
657
|
-
|
|
832
|
+
}
|
|
833
|
+
if (trimmed.startsWith("/")) {
|
|
658
834
|
return true;
|
|
835
|
+
}
|
|
659
836
|
return /^[A-Za-z]:[\\/]/.test(trimmed);
|
|
660
837
|
}
|
|
661
838
|
function defaultFileExists(filePath) {
|
|
@@ -667,40 +844,48 @@ function defaultFileExists(filePath) {
|
|
|
667
844
|
}
|
|
668
845
|
}
|
|
669
846
|
export function normalizeSafeBins(entries) {
|
|
670
|
-
if (!Array.isArray(entries))
|
|
847
|
+
if (!Array.isArray(entries)) {
|
|
671
848
|
return new Set();
|
|
849
|
+
}
|
|
672
850
|
const normalized = entries
|
|
673
851
|
.map((entry) => entry.trim().toLowerCase())
|
|
674
852
|
.filter((entry) => entry.length > 0);
|
|
675
853
|
return new Set(normalized);
|
|
676
854
|
}
|
|
677
855
|
export function resolveSafeBins(entries) {
|
|
678
|
-
if (entries === undefined)
|
|
856
|
+
if (entries === undefined) {
|
|
679
857
|
return normalizeSafeBins(DEFAULT_SAFE_BINS);
|
|
858
|
+
}
|
|
680
859
|
return normalizeSafeBins(entries ?? []);
|
|
681
860
|
}
|
|
682
861
|
export function isSafeBinUsage(params) {
|
|
683
|
-
if (params.safeBins.size === 0)
|
|
862
|
+
if (params.safeBins.size === 0) {
|
|
684
863
|
return false;
|
|
864
|
+
}
|
|
685
865
|
const resolution = params.resolution;
|
|
686
866
|
const execName = resolution?.executableName?.toLowerCase();
|
|
687
|
-
if (!execName)
|
|
867
|
+
if (!execName) {
|
|
688
868
|
return false;
|
|
869
|
+
}
|
|
689
870
|
const matchesSafeBin = params.safeBins.has(execName) ||
|
|
690
871
|
(process.platform === "win32" && params.safeBins.has(path.parse(execName).name));
|
|
691
|
-
if (!matchesSafeBin)
|
|
872
|
+
if (!matchesSafeBin) {
|
|
692
873
|
return false;
|
|
693
|
-
|
|
874
|
+
}
|
|
875
|
+
if (!resolution?.resolvedPath) {
|
|
694
876
|
return false;
|
|
877
|
+
}
|
|
695
878
|
const cwd = params.cwd ?? process.cwd();
|
|
696
879
|
const exists = params.fileExists ?? defaultFileExists;
|
|
697
880
|
const argv = params.argv.slice(1);
|
|
698
881
|
for (let i = 0; i < argv.length; i += 1) {
|
|
699
882
|
const token = argv[i];
|
|
700
|
-
if (!token)
|
|
883
|
+
if (!token) {
|
|
701
884
|
continue;
|
|
702
|
-
|
|
885
|
+
}
|
|
886
|
+
if (token === "-") {
|
|
703
887
|
continue;
|
|
888
|
+
}
|
|
704
889
|
if (token.startsWith("-")) {
|
|
705
890
|
const eqIndex = token.indexOf("=");
|
|
706
891
|
if (eqIndex > 0) {
|
|
@@ -711,10 +896,12 @@ export function isSafeBinUsage(params) {
|
|
|
711
896
|
}
|
|
712
897
|
continue;
|
|
713
898
|
}
|
|
714
|
-
if (isPathLikeToken(token))
|
|
899
|
+
if (isPathLikeToken(token)) {
|
|
715
900
|
return false;
|
|
716
|
-
|
|
901
|
+
}
|
|
902
|
+
if (exists(path.resolve(cwd, token))) {
|
|
717
903
|
return false;
|
|
904
|
+
}
|
|
718
905
|
}
|
|
719
906
|
return true;
|
|
720
907
|
}
|
|
@@ -727,8 +914,9 @@ function evaluateSegments(segments, params) {
|
|
|
727
914
|
? { ...segment.resolution, resolvedPath: candidatePath }
|
|
728
915
|
: segment.resolution;
|
|
729
916
|
const match = matchAllowlist(params.allowlist, candidateResolution);
|
|
730
|
-
if (match)
|
|
917
|
+
if (match) {
|
|
731
918
|
matches.push(match);
|
|
919
|
+
}
|
|
732
920
|
const safe = isSafeBinUsage({
|
|
733
921
|
argv: segment.argv,
|
|
734
922
|
resolution: segment.resolution,
|
|
@@ -798,6 +986,7 @@ function splitCommandChain(command) {
|
|
|
798
986
|
};
|
|
799
987
|
for (let i = 0; i < command.length; i += 1) {
|
|
800
988
|
const ch = command[i];
|
|
989
|
+
const next = command[i + 1];
|
|
801
990
|
if (escaped) {
|
|
802
991
|
buf += ch;
|
|
803
992
|
escaped = false;
|
|
@@ -809,14 +998,22 @@ function splitCommandChain(command) {
|
|
|
809
998
|
continue;
|
|
810
999
|
}
|
|
811
1000
|
if (inSingle) {
|
|
812
|
-
if (ch === "'")
|
|
1001
|
+
if (ch === "'") {
|
|
813
1002
|
inSingle = false;
|
|
1003
|
+
}
|
|
814
1004
|
buf += ch;
|
|
815
1005
|
continue;
|
|
816
1006
|
}
|
|
817
1007
|
if (inDouble) {
|
|
818
|
-
if (ch ===
|
|
1008
|
+
if (ch === "\\" && isDoubleQuoteEscape(next)) {
|
|
1009
|
+
buf += ch;
|
|
1010
|
+
buf += next;
|
|
1011
|
+
i += 1;
|
|
1012
|
+
continue;
|
|
1013
|
+
}
|
|
1014
|
+
if (ch === '"') {
|
|
819
1015
|
inDouble = false;
|
|
1016
|
+
}
|
|
820
1017
|
buf += ch;
|
|
821
1018
|
continue;
|
|
822
1019
|
}
|
|
@@ -831,44 +1028,50 @@ function splitCommandChain(command) {
|
|
|
831
1028
|
continue;
|
|
832
1029
|
}
|
|
833
1030
|
if (ch === "&" && command[i + 1] === "&") {
|
|
834
|
-
if (!pushPart())
|
|
1031
|
+
if (!pushPart()) {
|
|
835
1032
|
invalidChain = true;
|
|
1033
|
+
}
|
|
836
1034
|
i += 1;
|
|
837
1035
|
foundChain = true;
|
|
838
1036
|
continue;
|
|
839
1037
|
}
|
|
840
1038
|
if (ch === "|" && command[i + 1] === "|") {
|
|
841
|
-
if (!pushPart())
|
|
1039
|
+
if (!pushPart()) {
|
|
842
1040
|
invalidChain = true;
|
|
1041
|
+
}
|
|
843
1042
|
i += 1;
|
|
844
1043
|
foundChain = true;
|
|
845
1044
|
continue;
|
|
846
1045
|
}
|
|
847
1046
|
if (ch === ";") {
|
|
848
|
-
if (!pushPart())
|
|
1047
|
+
if (!pushPart()) {
|
|
849
1048
|
invalidChain = true;
|
|
1049
|
+
}
|
|
850
1050
|
foundChain = true;
|
|
851
1051
|
continue;
|
|
852
1052
|
}
|
|
853
1053
|
buf += ch;
|
|
854
1054
|
}
|
|
855
1055
|
const pushedFinal = pushPart();
|
|
856
|
-
if (!foundChain)
|
|
1056
|
+
if (!foundChain) {
|
|
857
1057
|
return null;
|
|
858
|
-
|
|
1058
|
+
}
|
|
1059
|
+
if (invalidChain || !pushedFinal) {
|
|
859
1060
|
return null;
|
|
1061
|
+
}
|
|
860
1062
|
return parts.length > 0 ? parts : null;
|
|
861
1063
|
}
|
|
862
1064
|
/**
|
|
863
1065
|
* Evaluates allowlist for shell commands (including &&, ||, ;) and returns analysis metadata.
|
|
864
1066
|
*/
|
|
865
1067
|
export function evaluateShellAllowlist(params) {
|
|
866
|
-
const chainParts = splitCommandChain(params.command);
|
|
1068
|
+
const chainParts = isWindowsPlatform(params.platform) ? null : splitCommandChain(params.command);
|
|
867
1069
|
if (!chainParts) {
|
|
868
1070
|
const analysis = analyzeShellCommand({
|
|
869
1071
|
command: params.command,
|
|
870
1072
|
cwd: params.cwd,
|
|
871
1073
|
env: params.env,
|
|
1074
|
+
platform: params.platform,
|
|
872
1075
|
});
|
|
873
1076
|
if (!analysis.ok) {
|
|
874
1077
|
return {
|
|
@@ -900,6 +1103,7 @@ export function evaluateShellAllowlist(params) {
|
|
|
900
1103
|
command: part,
|
|
901
1104
|
cwd: params.cwd,
|
|
902
1105
|
env: params.env,
|
|
1106
|
+
platform: params.platform,
|
|
903
1107
|
});
|
|
904
1108
|
if (!analysis.ok) {
|
|
905
1109
|
return {
|
|
@@ -965,10 +1169,12 @@ export function addAllowlistEntry(approvals, agentId, pattern) {
|
|
|
965
1169
|
const existing = agents[target] ?? {};
|
|
966
1170
|
const allowlist = Array.isArray(existing.allowlist) ? existing.allowlist : [];
|
|
967
1171
|
const trimmed = pattern.trim();
|
|
968
|
-
if (!trimmed)
|
|
1172
|
+
if (!trimmed) {
|
|
969
1173
|
return;
|
|
970
|
-
|
|
1174
|
+
}
|
|
1175
|
+
if (allowlist.some((entry) => entry.pattern === trimmed)) {
|
|
971
1176
|
return;
|
|
1177
|
+
}
|
|
972
1178
|
allowlist.push({ id: crypto.randomUUID(), pattern: trimmed, lastUsedAt: Date.now() });
|
|
973
1179
|
agents[target] = { ...existing, allowlist };
|
|
974
1180
|
approvals.agents = agents;
|
|
@@ -984,16 +1190,18 @@ export function maxAsk(a, b) {
|
|
|
984
1190
|
}
|
|
985
1191
|
export async function requestExecApprovalViaSocket(params) {
|
|
986
1192
|
const { socketPath, token, request } = params;
|
|
987
|
-
if (!socketPath || !token)
|
|
1193
|
+
if (!socketPath || !token) {
|
|
988
1194
|
return null;
|
|
1195
|
+
}
|
|
989
1196
|
const timeoutMs = params.timeoutMs ?? 15_000;
|
|
990
1197
|
return await new Promise((resolve) => {
|
|
991
1198
|
const client = new net.Socket();
|
|
992
1199
|
let settled = false;
|
|
993
1200
|
let buffer = "";
|
|
994
1201
|
const finish = (value) => {
|
|
995
|
-
if (settled)
|
|
1202
|
+
if (settled) {
|
|
996
1203
|
return;
|
|
1204
|
+
}
|
|
997
1205
|
settled = true;
|
|
998
1206
|
try {
|
|
999
1207
|
client.destroy();
|
|
@@ -1021,8 +1229,9 @@ export async function requestExecApprovalViaSocket(params) {
|
|
|
1021
1229
|
const line = buffer.slice(0, idx).trim();
|
|
1022
1230
|
buffer = buffer.slice(idx + 1);
|
|
1023
1231
|
idx = buffer.indexOf("\n");
|
|
1024
|
-
if (!line)
|
|
1232
|
+
if (!line) {
|
|
1025
1233
|
continue;
|
|
1234
|
+
}
|
|
1026
1235
|
try {
|
|
1027
1236
|
const msg = JSON.parse(line);
|
|
1028
1237
|
if (msg?.type === "decision" && msg.decision) {
|