@poolzin/pool-bot 2026.2.24 → 2026.2.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/dist/acp/client.js +207 -18
- package/dist/acp/secret-file.js +22 -0
- package/dist/agents/agent-scope.js +10 -0
- package/dist/agents/bash-process-registry.test-helpers.js +29 -0
- package/dist/agents/bash-tools.exec-approval-request.js +20 -0
- package/dist/agents/bash-tools.exec-host-gateway.js +230 -0
- package/dist/agents/bash-tools.exec-host-node.js +235 -0
- package/dist/agents/bash-tools.exec-types.js +1 -0
- package/dist/agents/bash-tools.process.js +224 -218
- package/dist/agents/content-blocks.js +16 -0
- package/dist/agents/model-fallback.js +96 -101
- package/dist/agents/models-config.providers.js +299 -182
- package/dist/agents/pi-embedded-payloads.js +1 -0
- package/dist/agents/pi-embedded-runner/run.overflow-compaction.fixture.js +34 -0
- package/dist/agents/skills.test-helpers.js +13 -0
- package/dist/agents/stable-stringify.js +12 -0
- package/dist/agents/subagent-registry.mocks.shared.js +12 -0
- package/dist/agents/test-helpers/assistant-message-fixtures.js +29 -0
- package/dist/agents/test-helpers/pi-tools-sandbox-context.js +27 -0
- package/dist/agents/tool-policy-shared.js +108 -0
- package/dist/agents/tools/browser-tool.js +160 -54
- package/dist/agents/tools/cron-tool.test-helpers.js +12 -0
- package/dist/agents/tools/discord-actions-moderation-shared.js +27 -0
- package/dist/agents/tools/image-tool.js +214 -99
- package/dist/agents/tools/sessions-history-tool.js +140 -108
- package/dist/agents/workspace.js +222 -46
- package/dist/auto-reply/commands-registry.js +15 -18
- package/dist/auto-reply/fallback-state.js +114 -0
- package/dist/auto-reply/model-runtime.js +68 -0
- package/dist/auto-reply/reply/agent-runner-execution.js +36 -4
- package/dist/auto-reply/reply/agent-runner.js +165 -39
- package/dist/auto-reply/reply/commands-setunset-standard.js +13 -0
- package/dist/browser/config.js +26 -0
- package/dist/browser/navigation-guard.js +31 -0
- package/dist/browser/routes/agent.act.js +431 -424
- package/dist/browser/routes/agent.shared.js +47 -3
- package/dist/browser/routes/agent.snapshot.js +122 -116
- package/dist/browser/routes/agent.storage.js +303 -297
- package/dist/browser/routes/tabs.js +154 -100
- package/dist/browser/server-lifecycle.js +37 -0
- package/dist/build-info.json +3 -3
- package/dist/channels/allow-from.js +25 -0
- package/dist/channels/plugins/account-action-gate.js +13 -0
- package/dist/channels/plugins/message-actions.js +10 -0
- package/dist/channels/telegram/api.js +18 -0
- package/dist/cli/argv.js +84 -21
- package/dist/cli/banner.js +2 -1
- package/dist/cli/exec-approvals-cli.js +92 -124
- package/dist/cli/memory-cli.js +158 -61
- package/dist/cli/nodes-cli/register.push.js +63 -0
- package/dist/cli/nodes-media-utils.js +21 -0
- package/dist/cli/plugins-cli.js +245 -61
- package/dist/cli/program/build-program.js +3 -1
- package/dist/cli/program/command-registry.js +223 -136
- package/dist/cli/program/help.js +43 -12
- package/dist/cli/route.js +1 -1
- package/dist/cli/test-runtime-capture.js +24 -0
- package/dist/commands/agent.js +163 -87
- package/dist/commands/channels.mock-harness.js +23 -0
- package/dist/commands/daemon-install-runtime-warning.js +11 -0
- package/dist/commands/sessions.test-helpers.js +61 -0
- package/dist/config/commands.js +3 -0
- package/dist/config/config.js +1 -1
- package/dist/config/env-substitution.js +62 -34
- package/dist/config/env-vars.js +9 -0
- package/dist/config/io.js +571 -171
- package/dist/config/merge-patch.js +50 -4
- package/dist/config/redact-snapshot.js +404 -76
- package/dist/config/schema.js +58 -570
- package/dist/config/validation.js +140 -85
- package/dist/config/zod-schema.hooks.js +40 -11
- package/dist/config/zod-schema.installs.js +20 -0
- package/dist/config/zod-schema.js +8 -7
- package/dist/daemon/cmd-argv.js +21 -0
- package/dist/daemon/cmd-set.js +58 -0
- package/dist/daemon/service-types.js +1 -0
- package/dist/discord/monitor/exec-approvals.js +357 -162
- package/dist/gateway/auth.js +38 -3
- package/dist/gateway/call.js +149 -68
- package/dist/gateway/canvas-capability.js +75 -0
- package/dist/gateway/control-plane-audit.js +28 -0
- package/dist/gateway/control-plane-rate-limit.js +53 -0
- package/dist/gateway/events.js +1 -0
- package/dist/gateway/hooks.js +109 -54
- package/dist/gateway/http-common.js +22 -0
- package/dist/gateway/method-scopes.js +169 -0
- package/dist/gateway/net.js +23 -0
- package/dist/gateway/openresponses-http.js +120 -110
- package/dist/gateway/probe-auth.js +2 -0
- package/dist/gateway/protocol/index.js +3 -2
- package/dist/gateway/protocol/schema/protocol-schemas.js +2 -0
- package/dist/gateway/protocol/schema/push.js +18 -0
- package/dist/gateway/protocol/schema.js +1 -0
- package/dist/gateway/server-http.js +236 -52
- package/dist/gateway/server-methods/agent.js +162 -24
- package/dist/gateway/server-methods/chat.js +461 -130
- package/dist/gateway/server-methods/config.js +193 -150
- package/dist/gateway/server-methods/nodes.helpers.js +12 -0
- package/dist/gateway/server-methods/nodes.js +251 -69
- package/dist/gateway/server-methods/push.js +53 -0
- package/dist/gateway/server-reload-handlers.js +2 -3
- package/dist/gateway/server-runtime-config.js +5 -0
- package/dist/gateway/server-runtime-state.js +2 -0
- package/dist/gateway/server-ws-runtime.js +1 -0
- package/dist/gateway/server.impl.js +296 -139
- package/dist/gateway/session-preview.test-helpers.js +11 -0
- package/dist/gateway/startup-auth.js +126 -0
- package/dist/gateway/test-helpers.agent-results.js +15 -0
- package/dist/gateway/test-helpers.mocks.js +37 -14
- package/dist/gateway/test-helpers.server.js +161 -77
- package/dist/hooks/bundled/session-memory/handler.js +165 -34
- package/dist/hooks/gmail-watcher-lifecycle.js +23 -0
- package/dist/infra/archive-path.js +49 -0
- package/dist/infra/device-pairing.js +148 -167
- package/dist/infra/exec-approvals-allowlist.js +19 -70
- package/dist/infra/exec-approvals-analysis.js +44 -17
- package/dist/infra/exec-safe-bin-policy.js +269 -0
- package/dist/infra/fixed-window-rate-limit.js +33 -0
- package/dist/infra/git-root.js +61 -0
- package/dist/infra/heartbeat-active-hours.js +2 -2
- package/dist/infra/heartbeat-reason.js +40 -0
- package/dist/infra/heartbeat-runner.js +72 -32
- package/dist/infra/install-source-utils.js +91 -7
- package/dist/infra/node-pairing.js +50 -105
- package/dist/infra/npm-integrity.js +45 -0
- package/dist/infra/npm-pack-install.js +40 -0
- package/dist/infra/outbound/channel-adapters.js +20 -7
- package/dist/infra/outbound/message-action-runner.js +107 -327
- package/dist/infra/outbound/message.js +59 -36
- package/dist/infra/outbound/outbound-policy.js +52 -25
- package/dist/infra/outbound/outbound-send-service.js +58 -71
- package/dist/infra/pairing-files.js +10 -0
- package/dist/infra/plain-object.js +9 -0
- package/dist/infra/push-apns.js +365 -0
- package/dist/infra/restart-sentinel.js +16 -1
- package/dist/infra/restart.js +229 -26
- package/dist/infra/scp-host.js +54 -0
- package/dist/infra/update-startup.js +86 -9
- package/dist/media/inbound-path-policy.js +114 -0
- package/dist/media/input-files.js +16 -0
- package/dist/memory/test-manager.js +8 -0
- package/dist/plugin-sdk/temp-path.js +47 -0
- package/dist/plugins/discovery.js +217 -23
- package/dist/plugins/hook-runner-global.js +16 -0
- package/dist/plugins/loader.js +192 -26
- package/dist/plugins/logger.js +8 -0
- package/dist/plugins/manifest-registry.js +3 -0
- package/dist/plugins/path-safety.js +34 -0
- package/dist/plugins/registry.js +5 -2
- package/dist/plugins/runtime/index.js +271 -206
- package/dist/providers/github-copilot-models.js +4 -1
- package/dist/security/audit-channel.js +8 -19
- package/dist/security/audit-extra.async.js +354 -182
- package/dist/security/audit-extra.js +11 -1
- package/dist/security/audit-extra.sync.js +340 -33
- package/dist/security/audit-fs.js +31 -13
- package/dist/security/audit.js +145 -371
- package/dist/security/dm-policy-shared.js +24 -0
- package/dist/security/external-content.js +20 -8
- package/dist/security/fix.js +49 -85
- package/dist/security/scan-paths.js +20 -0
- package/dist/security/secret-equal.js +3 -7
- package/dist/security/windows-acl.js +30 -15
- package/dist/shared/node-list-parse.js +13 -0
- package/dist/shared/operator-scope-compat.js +37 -0
- package/dist/shared/text-chunking.js +29 -0
- package/dist/slack/blocks.test-helpers.js +31 -0
- package/dist/slack/monitor/mrkdwn.js +8 -0
- package/dist/telegram/bot-message-dispatch.js +366 -164
- package/dist/telegram/draft-stream.js +30 -7
- package/dist/telegram/reasoning-lane-coordinator.js +128 -0
- package/dist/terminal/prompt-select-styled.js +9 -0
- package/dist/test-utils/command-runner.js +6 -0
- package/dist/test-utils/internal-hook-event-payload.js +10 -0
- package/dist/test-utils/model-auth-mock.js +12 -0
- package/dist/test-utils/provider-usage-fetch.js +14 -0
- package/dist/test-utils/temp-home.js +33 -0
- package/dist/tui/components/chat-log.js +9 -0
- package/dist/tui/tui-command-handlers.js +36 -27
- package/dist/tui/tui-event-handlers.js +122 -32
- package/dist/tui/tui.js +181 -45
- package/dist/utils/mask-api-key.js +10 -0
- package/dist/utils/run-with-concurrency.js +39 -0
- package/dist/web/media.js +4 -0
- package/docs/tools/slash-commands.md +5 -1
- package/extensions/feishu/src/external-keys.ts +19 -0
- package/extensions/lobster/src/windows-spawn.ts +193 -0
- package/extensions/matrix/src/matrix/actions/limits.ts +6 -0
- package/extensions/mattermost/src/mattermost/reactions.test-helpers.ts +83 -0
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@ import { CHANNEL_IDS, normalizeChatChannelId } from "../channels/registry.js";
|
|
|
4
4
|
import { normalizePluginsConfig, resolveEnableState, resolveMemorySlotDecision, } from "../plugins/config-state.js";
|
|
5
5
|
import { loadPluginManifestRegistry } from "../plugins/manifest-registry.js";
|
|
6
6
|
import { validateJsonSchemaValue } from "../plugins/schema-validator.js";
|
|
7
|
+
import { isRecord } from "../utils.js";
|
|
7
8
|
import { findDuplicateAgentDirs, formatDuplicateAgentDirError } from "./agent-dirs.js";
|
|
8
9
|
import { applyAgentDefaults, applyModelDefaults, applySessionDefaults } from "./defaults.js";
|
|
9
10
|
import { findLegacyConfigIssues } from "./legacy.js";
|
|
@@ -16,28 +17,35 @@ function isWorkspaceAvatarPath(value, workspaceDir) {
|
|
|
16
17
|
const workspaceRoot = path.resolve(workspaceDir);
|
|
17
18
|
const resolved = path.resolve(workspaceRoot, value);
|
|
18
19
|
const relative = path.relative(workspaceRoot, resolved);
|
|
19
|
-
if (relative === "")
|
|
20
|
+
if (relative === "") {
|
|
20
21
|
return true;
|
|
21
|
-
|
|
22
|
+
}
|
|
23
|
+
if (relative.startsWith("..")) {
|
|
22
24
|
return false;
|
|
25
|
+
}
|
|
23
26
|
return !path.isAbsolute(relative);
|
|
24
27
|
}
|
|
25
28
|
function validateIdentityAvatar(config) {
|
|
26
29
|
const agents = config.agents?.list;
|
|
27
|
-
if (!Array.isArray(agents) || agents.length === 0)
|
|
30
|
+
if (!Array.isArray(agents) || agents.length === 0) {
|
|
28
31
|
return [];
|
|
32
|
+
}
|
|
29
33
|
const issues = [];
|
|
30
34
|
for (const [index, entry] of agents.entries()) {
|
|
31
|
-
if (!entry || typeof entry !== "object")
|
|
35
|
+
if (!entry || typeof entry !== "object") {
|
|
32
36
|
continue;
|
|
37
|
+
}
|
|
33
38
|
const avatarRaw = entry.identity?.avatar;
|
|
34
|
-
if (typeof avatarRaw !== "string")
|
|
39
|
+
if (typeof avatarRaw !== "string") {
|
|
35
40
|
continue;
|
|
41
|
+
}
|
|
36
42
|
const avatar = avatarRaw.trim();
|
|
37
|
-
if (!avatar)
|
|
43
|
+
if (!avatar) {
|
|
38
44
|
continue;
|
|
39
|
-
|
|
45
|
+
}
|
|
46
|
+
if (AVATAR_DATA_RE.test(avatar) || AVATAR_HTTP_RE.test(avatar)) {
|
|
40
47
|
continue;
|
|
48
|
+
}
|
|
41
49
|
if (avatar.startsWith("~")) {
|
|
42
50
|
issues.push({
|
|
43
51
|
path: `agents.list.${index}.identity.avatar`,
|
|
@@ -63,7 +71,11 @@ function validateIdentityAvatar(config) {
|
|
|
63
71
|
}
|
|
64
72
|
return issues;
|
|
65
73
|
}
|
|
66
|
-
|
|
74
|
+
/**
|
|
75
|
+
* Validates config without applying runtime defaults.
|
|
76
|
+
* Use this when you need the raw validated config (e.g., for writing back to file).
|
|
77
|
+
*/
|
|
78
|
+
export function validateConfigObjectRaw(raw) {
|
|
67
79
|
const legacyIssues = findLegacyConfigIssues(raw);
|
|
68
80
|
if (legacyIssues.length > 0) {
|
|
69
81
|
return {
|
|
@@ -102,42 +114,136 @@ export function validateConfigObject(raw) {
|
|
|
102
114
|
}
|
|
103
115
|
return {
|
|
104
116
|
ok: true,
|
|
105
|
-
config:
|
|
117
|
+
config: validated.data,
|
|
106
118
|
};
|
|
107
119
|
}
|
|
108
|
-
function
|
|
109
|
-
|
|
120
|
+
export function validateConfigObject(raw) {
|
|
121
|
+
const result = validateConfigObjectRaw(raw);
|
|
122
|
+
if (!result.ok) {
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
ok: true,
|
|
127
|
+
config: applyModelDefaults(applyAgentDefaults(applySessionDefaults(result.config))),
|
|
128
|
+
};
|
|
110
129
|
}
|
|
111
130
|
export function validateConfigObjectWithPlugins(raw) {
|
|
112
|
-
|
|
131
|
+
return validateConfigObjectWithPluginsBase(raw, { applyDefaults: true });
|
|
132
|
+
}
|
|
133
|
+
export function validateConfigObjectRawWithPlugins(raw) {
|
|
134
|
+
return validateConfigObjectWithPluginsBase(raw, { applyDefaults: false });
|
|
135
|
+
}
|
|
136
|
+
function validateConfigObjectWithPluginsBase(raw, opts) {
|
|
137
|
+
const base = opts.applyDefaults ? validateConfigObject(raw) : validateConfigObjectRaw(raw);
|
|
113
138
|
if (!base.ok) {
|
|
114
139
|
return { ok: false, issues: base.issues, warnings: [] };
|
|
115
140
|
}
|
|
116
141
|
const config = base.config;
|
|
117
142
|
const issues = [];
|
|
118
143
|
const warnings = [];
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
144
|
+
const hasExplicitPluginsConfig = isRecord(raw) && Object.prototype.hasOwnProperty.call(raw, "plugins");
|
|
145
|
+
let registryInfo = null;
|
|
146
|
+
const ensureRegistry = () => {
|
|
147
|
+
if (registryInfo) {
|
|
148
|
+
return registryInfo;
|
|
149
|
+
}
|
|
150
|
+
const workspaceDir = resolveAgentWorkspaceDir(config, resolveDefaultAgentId(config));
|
|
151
|
+
const registry = loadPluginManifestRegistry({
|
|
152
|
+
config,
|
|
153
|
+
workspaceDir: workspaceDir ?? undefined,
|
|
154
|
+
});
|
|
155
|
+
const knownIds = new Set(registry.plugins.map((record) => record.id));
|
|
156
|
+
const normalizedPlugins = normalizePluginsConfig(config.plugins);
|
|
157
|
+
for (const diag of registry.diagnostics) {
|
|
158
|
+
let path = diag.pluginId ? `plugins.entries.${diag.pluginId}` : "plugins";
|
|
159
|
+
if (!diag.pluginId && diag.message.includes("plugin path not found")) {
|
|
160
|
+
path = "plugins.load.paths";
|
|
161
|
+
}
|
|
162
|
+
const pluginLabel = diag.pluginId ? `plugin ${diag.pluginId}` : "plugin";
|
|
163
|
+
const message = `${pluginLabel}: ${diag.message}`;
|
|
164
|
+
if (diag.level === "error") {
|
|
165
|
+
issues.push({ path, message });
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
warnings.push({ path, message });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
registryInfo = { registry, knownIds, normalizedPlugins };
|
|
172
|
+
return registryInfo;
|
|
173
|
+
};
|
|
174
|
+
const allowedChannels = new Set(["defaults", ...CHANNEL_IDS]);
|
|
175
|
+
if (config.channels && isRecord(config.channels)) {
|
|
176
|
+
for (const key of Object.keys(config.channels)) {
|
|
177
|
+
const trimmed = key.trim();
|
|
178
|
+
if (!trimmed) {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (!allowedChannels.has(trimmed)) {
|
|
182
|
+
const { registry } = ensureRegistry();
|
|
183
|
+
for (const record of registry.plugins) {
|
|
184
|
+
for (const channelId of record.channels) {
|
|
185
|
+
allowedChannels.add(channelId);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (!allowedChannels.has(trimmed)) {
|
|
190
|
+
issues.push({
|
|
191
|
+
path: `channels.${trimmed}`,
|
|
192
|
+
message: `unknown channel id: ${trimmed}`,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
const heartbeatChannelIds = new Set();
|
|
198
|
+
for (const channelId of CHANNEL_IDS) {
|
|
199
|
+
heartbeatChannelIds.add(channelId.toLowerCase());
|
|
200
|
+
}
|
|
201
|
+
const validateHeartbeatTarget = (target, path) => {
|
|
202
|
+
if (typeof target !== "string") {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const trimmed = target.trim();
|
|
206
|
+
if (!trimmed) {
|
|
207
|
+
issues.push({ path, message: "heartbeat target must not be empty" });
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const normalized = trimmed.toLowerCase();
|
|
211
|
+
if (normalized === "last" || normalized === "none") {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (normalizeChatChannelId(trimmed)) {
|
|
215
|
+
return;
|
|
131
216
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
217
|
+
if (!heartbeatChannelIds.has(normalized)) {
|
|
218
|
+
const { registry } = ensureRegistry();
|
|
219
|
+
for (const record of registry.plugins) {
|
|
220
|
+
for (const channelId of record.channels) {
|
|
221
|
+
const pluginChannel = channelId.trim();
|
|
222
|
+
if (pluginChannel) {
|
|
223
|
+
heartbeatChannelIds.add(pluginChannel.toLowerCase());
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
136
227
|
}
|
|
137
|
-
|
|
138
|
-
|
|
228
|
+
if (heartbeatChannelIds.has(normalized)) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
issues.push({ path, message: `unknown heartbeat target: ${target}` });
|
|
232
|
+
};
|
|
233
|
+
validateHeartbeatTarget(config.agents?.defaults?.heartbeat?.target, "agents.defaults.heartbeat.target");
|
|
234
|
+
if (Array.isArray(config.agents?.list)) {
|
|
235
|
+
for (const [index, entry] of config.agents.list.entries()) {
|
|
236
|
+
validateHeartbeatTarget(entry?.heartbeat?.target, `agents.list.${index}.heartbeat.target`);
|
|
139
237
|
}
|
|
140
238
|
}
|
|
239
|
+
if (!hasExplicitPluginsConfig) {
|
|
240
|
+
if (issues.length > 0) {
|
|
241
|
+
return { ok: false, issues, warnings };
|
|
242
|
+
}
|
|
243
|
+
return { ok: true, config, warnings };
|
|
244
|
+
}
|
|
245
|
+
const { registry, knownIds, normalizedPlugins } = ensureRegistry();
|
|
246
|
+
const pluginsConfig = config.plugins;
|
|
141
247
|
const entries = pluginsConfig?.entries;
|
|
142
248
|
if (entries && isRecord(entries)) {
|
|
143
249
|
for (const pluginId of Object.keys(entries)) {
|
|
@@ -151,8 +257,9 @@ export function validateConfigObjectWithPlugins(raw) {
|
|
|
151
257
|
}
|
|
152
258
|
const allow = pluginsConfig?.allow ?? [];
|
|
153
259
|
for (const pluginId of allow) {
|
|
154
|
-
if (typeof pluginId !== "string" || !pluginId.trim())
|
|
260
|
+
if (typeof pluginId !== "string" || !pluginId.trim()) {
|
|
155
261
|
continue;
|
|
262
|
+
}
|
|
156
263
|
if (!knownIds.has(pluginId)) {
|
|
157
264
|
issues.push({
|
|
158
265
|
path: "plugins.allow",
|
|
@@ -162,8 +269,9 @@ export function validateConfigObjectWithPlugins(raw) {
|
|
|
162
269
|
}
|
|
163
270
|
const deny = pluginsConfig?.deny ?? [];
|
|
164
271
|
for (const pluginId of deny) {
|
|
165
|
-
if (typeof pluginId !== "string" || !pluginId.trim())
|
|
272
|
+
if (typeof pluginId !== "string" || !pluginId.trim()) {
|
|
166
273
|
continue;
|
|
274
|
+
}
|
|
167
275
|
if (!knownIds.has(pluginId)) {
|
|
168
276
|
issues.push({
|
|
169
277
|
path: "plugins.deny",
|
|
@@ -178,59 +286,6 @@ export function validateConfigObjectWithPlugins(raw) {
|
|
|
178
286
|
message: `plugin not found: ${memorySlot}`,
|
|
179
287
|
});
|
|
180
288
|
}
|
|
181
|
-
const allowedChannels = new Set(["defaults", ...CHANNEL_IDS]);
|
|
182
|
-
for (const record of registry.plugins) {
|
|
183
|
-
for (const channelId of record.channels) {
|
|
184
|
-
allowedChannels.add(channelId);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
if (config.channels && isRecord(config.channels)) {
|
|
188
|
-
for (const key of Object.keys(config.channels)) {
|
|
189
|
-
const trimmed = key.trim();
|
|
190
|
-
if (!trimmed)
|
|
191
|
-
continue;
|
|
192
|
-
if (!allowedChannels.has(trimmed)) {
|
|
193
|
-
issues.push({
|
|
194
|
-
path: `channels.${trimmed}`,
|
|
195
|
-
message: `unknown channel id: ${trimmed}`,
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
const heartbeatChannelIds = new Set();
|
|
201
|
-
for (const channelId of CHANNEL_IDS) {
|
|
202
|
-
heartbeatChannelIds.add(channelId.toLowerCase());
|
|
203
|
-
}
|
|
204
|
-
for (const record of registry.plugins) {
|
|
205
|
-
for (const channelId of record.channels) {
|
|
206
|
-
const trimmed = channelId.trim();
|
|
207
|
-
if (trimmed)
|
|
208
|
-
heartbeatChannelIds.add(trimmed.toLowerCase());
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
const validateHeartbeatTarget = (target, path) => {
|
|
212
|
-
if (typeof target !== "string")
|
|
213
|
-
return;
|
|
214
|
-
const trimmed = target.trim();
|
|
215
|
-
if (!trimmed) {
|
|
216
|
-
issues.push({ path, message: "heartbeat target must not be empty" });
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
const normalized = trimmed.toLowerCase();
|
|
220
|
-
if (normalized === "last" || normalized === "none")
|
|
221
|
-
return;
|
|
222
|
-
if (normalizeChatChannelId(trimmed))
|
|
223
|
-
return;
|
|
224
|
-
if (heartbeatChannelIds.has(normalized))
|
|
225
|
-
return;
|
|
226
|
-
issues.push({ path, message: `unknown heartbeat target: ${target}` });
|
|
227
|
-
};
|
|
228
|
-
validateHeartbeatTarget(config.agents?.defaults?.heartbeat?.target, "agents.defaults.heartbeat.target");
|
|
229
|
-
if (Array.isArray(config.agents?.list)) {
|
|
230
|
-
for (const [index, entry] of config.agents.list.entries()) {
|
|
231
|
-
validateHeartbeatTarget(entry?.heartbeat?.target, `agents.list.${index}.heartbeat.target`);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
289
|
let selectedMemoryPluginId = null;
|
|
235
290
|
const seenPlugins = new Set();
|
|
236
291
|
for (const record of registry.plugins) {
|
|
@@ -1,4 +1,33 @@
|
|
|
1
|
+
import path from "node:path";
|
|
1
2
|
import { z } from "zod";
|
|
3
|
+
import { InstallRecordShape } from "./zod-schema.installs.js";
|
|
4
|
+
import { sensitive } from "./zod-schema.sensitive.js";
|
|
5
|
+
function isSafeRelativeModulePath(raw) {
|
|
6
|
+
const value = raw.trim();
|
|
7
|
+
if (!value) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
// Hook modules are loaded via file-path resolution + dynamic import().
|
|
11
|
+
// Keep this strictly relative to a configured base dir to avoid path traversal and surprises.
|
|
12
|
+
if (path.isAbsolute(value)) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
if (value.startsWith("~")) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
// Disallow URL-ish and drive-relative forms (e.g. "file:...", "C:foo").
|
|
19
|
+
if (value.includes(":")) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
const parts = value.split(/[\\/]+/g);
|
|
23
|
+
if (parts.some((part) => part === "..")) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
const SafeRelativeModulePathSchema = z
|
|
29
|
+
.string()
|
|
30
|
+
.refine(isSafeRelativeModulePath, "module must be a safe relative path (no absolute paths)");
|
|
2
31
|
export const HookMappingSchema = z
|
|
3
32
|
.object({
|
|
4
33
|
id: z.string().optional(),
|
|
@@ -11,7 +40,8 @@ export const HookMappingSchema = z
|
|
|
11
40
|
action: z.union([z.literal("wake"), z.literal("agent")]).optional(),
|
|
12
41
|
wakeMode: z.union([z.literal("now"), z.literal("next-heartbeat")]).optional(),
|
|
13
42
|
name: z.string().optional(),
|
|
14
|
-
|
|
43
|
+
agentId: z.string().optional(),
|
|
44
|
+
sessionKey: z.string().optional().register(sensitive),
|
|
15
45
|
messageTemplate: z.string().optional(),
|
|
16
46
|
textTemplate: z.string().optional(),
|
|
17
47
|
deliver: z.boolean().optional(),
|
|
@@ -22,6 +52,7 @@ export const HookMappingSchema = z
|
|
|
22
52
|
z.literal("whatsapp"),
|
|
23
53
|
z.literal("telegram"),
|
|
24
54
|
z.literal("discord"),
|
|
55
|
+
z.literal("irc"),
|
|
25
56
|
z.literal("slack"),
|
|
26
57
|
z.literal("signal"),
|
|
27
58
|
z.literal("imessage"),
|
|
@@ -34,7 +65,7 @@ export const HookMappingSchema = z
|
|
|
34
65
|
timeoutSeconds: z.number().int().positive().optional(),
|
|
35
66
|
transform: z
|
|
36
67
|
.object({
|
|
37
|
-
module:
|
|
68
|
+
module: SafeRelativeModulePathSchema,
|
|
38
69
|
export: z.string().optional(),
|
|
39
70
|
})
|
|
40
71
|
.strict()
|
|
@@ -45,7 +76,7 @@ export const HookMappingSchema = z
|
|
|
45
76
|
export const InternalHookHandlerSchema = z
|
|
46
77
|
.object({
|
|
47
78
|
event: z.string(),
|
|
48
|
-
module:
|
|
79
|
+
module: SafeRelativeModulePathSchema,
|
|
49
80
|
export: z.string().optional(),
|
|
50
81
|
})
|
|
51
82
|
.strict();
|
|
@@ -54,15 +85,13 @@ const HookConfigSchema = z
|
|
|
54
85
|
enabled: z.boolean().optional(),
|
|
55
86
|
env: z.record(z.string(), z.string()).optional(),
|
|
56
87
|
})
|
|
57
|
-
|
|
88
|
+
// Hook configs are intentionally open-ended (handlers can define their own keys).
|
|
89
|
+
// Keep enabled/env typed, but allow additional per-hook keys without marking the
|
|
90
|
+
// whole config invalid (which triggers doctor/best-effort loads).
|
|
91
|
+
.passthrough();
|
|
58
92
|
const HookInstallRecordSchema = z
|
|
59
93
|
.object({
|
|
60
|
-
|
|
61
|
-
spec: z.string().optional(),
|
|
62
|
-
sourcePath: z.string().optional(),
|
|
63
|
-
installPath: z.string().optional(),
|
|
64
|
-
version: z.string().optional(),
|
|
65
|
-
installedAt: z.string().optional(),
|
|
94
|
+
...InstallRecordShape,
|
|
66
95
|
hooks: z.array(z.string()).optional(),
|
|
67
96
|
})
|
|
68
97
|
.strict();
|
|
@@ -87,7 +116,7 @@ export const HooksGmailSchema = z
|
|
|
87
116
|
label: z.string().optional(),
|
|
88
117
|
topic: z.string().optional(),
|
|
89
118
|
subscription: z.string().optional(),
|
|
90
|
-
pushToken: z.string().optional(),
|
|
119
|
+
pushToken: z.string().optional().register(sensitive),
|
|
91
120
|
hookUrl: z.string().optional(),
|
|
92
121
|
includeBody: z.boolean().optional(),
|
|
93
122
|
maxBytes: z.number().int().positive().optional(),
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const InstallSourceSchema = z.union([
|
|
3
|
+
z.literal("npm"),
|
|
4
|
+
z.literal("archive"),
|
|
5
|
+
z.literal("path"),
|
|
6
|
+
]);
|
|
7
|
+
export const InstallRecordShape = {
|
|
8
|
+
source: InstallSourceSchema,
|
|
9
|
+
spec: z.string().optional(),
|
|
10
|
+
sourcePath: z.string().optional(),
|
|
11
|
+
installPath: z.string().optional(),
|
|
12
|
+
version: z.string().optional(),
|
|
13
|
+
resolvedName: z.string().optional(),
|
|
14
|
+
resolvedVersion: z.string().optional(),
|
|
15
|
+
resolvedSpec: z.string().optional(),
|
|
16
|
+
integrity: z.string().optional(),
|
|
17
|
+
shasum: z.string().optional(),
|
|
18
|
+
resolvedAt: z.string().optional(),
|
|
19
|
+
installedAt: z.string().optional(),
|
|
20
|
+
};
|
|
@@ -6,6 +6,7 @@ import { HexColorSchema, ModelsConfigSchema } from "./zod-schema.core.js";
|
|
|
6
6
|
import { HookMappingSchema, HooksGmailSchema, InternalHooksSchema } from "./zod-schema.hooks.js";
|
|
7
7
|
import { ChannelsSchema } from "./zod-schema.providers.js";
|
|
8
8
|
import { CommandsSchema, MessagesSchema, SessionSchema } from "./zod-schema.session.js";
|
|
9
|
+
import { sensitive } from "./zod-schema.sensitive.js";
|
|
9
10
|
const BrowserSnapshotDefaultsSchema = z
|
|
10
11
|
.object({
|
|
11
12
|
mode: z.literal("efficient").optional(),
|
|
@@ -226,7 +227,7 @@ export const PoolBotSchema = z
|
|
|
226
227
|
.object({
|
|
227
228
|
enabled: z.boolean().optional(),
|
|
228
229
|
path: z.string().optional(),
|
|
229
|
-
token: z.string().optional(),
|
|
230
|
+
token: z.string().optional().register(sensitive),
|
|
230
231
|
maxBodyBytes: z.number().int().positive().optional(),
|
|
231
232
|
presets: z.array(z.string()).optional(),
|
|
232
233
|
transformsDir: z.string().optional(),
|
|
@@ -286,7 +287,7 @@ export const PoolBotSchema = z
|
|
|
286
287
|
voiceAliases: z.record(z.string(), z.string()).optional(),
|
|
287
288
|
modelId: z.string().optional(),
|
|
288
289
|
outputFormat: z.string().optional(),
|
|
289
|
-
apiKey: z.string().optional(),
|
|
290
|
+
apiKey: z.string().optional().register(sensitive),
|
|
290
291
|
interruptOnSpeech: z.boolean().optional(),
|
|
291
292
|
})
|
|
292
293
|
.strict()
|
|
@@ -316,8 +317,8 @@ export const PoolBotSchema = z
|
|
|
316
317
|
auth: z
|
|
317
318
|
.object({
|
|
318
319
|
mode: z.union([z.literal("token"), z.literal("password")]).optional(),
|
|
319
|
-
token: z.string().optional(),
|
|
320
|
-
password: z.string().optional(),
|
|
320
|
+
token: z.string().optional().register(sensitive),
|
|
321
|
+
password: z.string().optional().register(sensitive),
|
|
321
322
|
allowTailscale: z.boolean().optional(),
|
|
322
323
|
})
|
|
323
324
|
.strict()
|
|
@@ -334,8 +335,8 @@ export const PoolBotSchema = z
|
|
|
334
335
|
.object({
|
|
335
336
|
url: z.string().optional(),
|
|
336
337
|
transport: z.union([z.literal("ssh"), z.literal("direct")]).optional(),
|
|
337
|
-
token: z.string().optional(),
|
|
338
|
-
password: z.string().optional(),
|
|
338
|
+
token: z.string().optional().register(sensitive),
|
|
339
|
+
password: z.string().optional().register(sensitive),
|
|
339
340
|
tlsFingerprint: z.string().optional(),
|
|
340
341
|
sshTarget: z.string().optional(),
|
|
341
342
|
sshIdentity: z.string().optional(),
|
|
@@ -460,7 +461,7 @@ export const PoolBotSchema = z
|
|
|
460
461
|
.record(z.string(), z
|
|
461
462
|
.object({
|
|
462
463
|
enabled: z.boolean().optional(),
|
|
463
|
-
apiKey: z.string().optional(),
|
|
464
|
+
apiKey: z.string().optional().register(sensitive),
|
|
464
465
|
env: z.record(z.string(), z.string()).optional(),
|
|
465
466
|
config: z.record(z.string(), z.unknown()).optional(),
|
|
466
467
|
})
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { splitArgsPreservingQuotes } from "./arg-split.js";
|
|
2
|
+
import { assertNoCmdLineBreak } from "./cmd-set.js";
|
|
3
|
+
export function quoteCmdScriptArg(value) {
|
|
4
|
+
assertNoCmdLineBreak(value, "Command argument");
|
|
5
|
+
if (!value) {
|
|
6
|
+
return '""';
|
|
7
|
+
}
|
|
8
|
+
const escaped = value.replace(/"/g, '\\"').replace(/%/g, "%%").replace(/!/g, "^!");
|
|
9
|
+
if (!/[ \t"&|<>^()%!]/g.test(value)) {
|
|
10
|
+
return escaped;
|
|
11
|
+
}
|
|
12
|
+
return `"${escaped}"`;
|
|
13
|
+
}
|
|
14
|
+
export function unescapeCmdScriptArg(value) {
|
|
15
|
+
return value.replace(/\^!/g, "!").replace(/%%/g, "%");
|
|
16
|
+
}
|
|
17
|
+
export function parseCmdScriptCommandLine(value) {
|
|
18
|
+
// Script renderer escapes quotes (`\"`) and cmd expansions (`%%`, `^!`).
|
|
19
|
+
// Keep all other backslashes literal so Windows drive/UNC paths survive.
|
|
20
|
+
return splitArgsPreservingQuotes(value, { escapeMode: "backslash-quote-only" }).map(unescapeCmdScriptArg);
|
|
21
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export function assertNoCmdLineBreak(value, field) {
|
|
2
|
+
if (/[\r\n]/.test(value)) {
|
|
3
|
+
throw new Error(`${field} cannot contain CR or LF in Windows task scripts.`);
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
function escapeCmdSetAssignmentComponent(value) {
|
|
7
|
+
return value.replace(/\^/g, "^^").replace(/%/g, "%%").replace(/!/g, "^!").replace(/"/g, '^"');
|
|
8
|
+
}
|
|
9
|
+
function unescapeCmdSetAssignmentComponent(value) {
|
|
10
|
+
let out = "";
|
|
11
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
12
|
+
const ch = value[i];
|
|
13
|
+
const next = value[i + 1];
|
|
14
|
+
if (ch === "^" && (next === "^" || next === '"' || next === "!")) {
|
|
15
|
+
out += next;
|
|
16
|
+
i += 1;
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (ch === "%" && next === "%") {
|
|
20
|
+
out += "%";
|
|
21
|
+
i += 1;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
out += ch;
|
|
25
|
+
}
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
export function parseCmdSetAssignment(line) {
|
|
29
|
+
const raw = line.trim();
|
|
30
|
+
if (!raw) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
const quoted = raw.startsWith('"') && raw.endsWith('"') && raw.length >= 2;
|
|
34
|
+
const assignment = quoted ? raw.slice(1, -1) : raw;
|
|
35
|
+
const index = assignment.indexOf("=");
|
|
36
|
+
if (index <= 0) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const key = assignment.slice(0, index).trim();
|
|
40
|
+
const value = assignment.slice(index + 1).trim();
|
|
41
|
+
if (!key) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
if (!quoted) {
|
|
45
|
+
return { key, value };
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
key: unescapeCmdSetAssignmentComponent(key),
|
|
49
|
+
value: unescapeCmdSetAssignmentComponent(value),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export function renderCmdSetAssignment(key, value) {
|
|
53
|
+
assertNoCmdLineBreak(key, "Environment variable name");
|
|
54
|
+
assertNoCmdLineBreak(value, "Environment variable value");
|
|
55
|
+
const escapedKey = escapeCmdSetAssignmentComponent(key);
|
|
56
|
+
const escapedValue = escapeCmdSetAssignmentComponent(value);
|
|
57
|
+
return `set "${escapedKey}=${escapedValue}"`;
|
|
58
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|