@poolzin/pool-bot 2026.2.23 → 2026.2.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/dist/acp/client.js +207 -18
- package/dist/acp/secret-file.js +22 -0
- package/dist/agents/agent-scope.js +10 -0
- package/dist/agents/bash-process-registry.test-helpers.js +29 -0
- package/dist/agents/bash-tools.exec-approval-request.js +20 -0
- package/dist/agents/bash-tools.exec-host-gateway.js +230 -0
- package/dist/agents/bash-tools.exec-host-node.js +235 -0
- package/dist/agents/bash-tools.exec-types.js +1 -0
- package/dist/agents/bash-tools.process.js +224 -218
- package/dist/agents/content-blocks.js +16 -0
- package/dist/agents/model-fallback.js +96 -101
- package/dist/agents/models-config.providers.js +299 -182
- package/dist/agents/pi-embedded-payloads.js +1 -0
- package/dist/agents/pi-embedded-runner/run.overflow-compaction.fixture.js +34 -0
- package/dist/agents/skills.test-helpers.js +13 -0
- package/dist/agents/stable-stringify.js +12 -0
- package/dist/agents/subagent-registry.mocks.shared.js +12 -0
- package/dist/agents/test-helpers/assistant-message-fixtures.js +29 -0
- package/dist/agents/test-helpers/pi-tools-sandbox-context.js +27 -0
- package/dist/agents/tool-policy-shared.js +108 -0
- package/dist/agents/tools/browser-tool.js +160 -54
- package/dist/agents/tools/cron-tool.test-helpers.js +12 -0
- package/dist/agents/tools/discord-actions-moderation-shared.js +27 -0
- package/dist/agents/tools/image-tool.js +214 -99
- package/dist/agents/tools/sessions-history-tool.js +140 -108
- package/dist/agents/workspace.js +222 -46
- package/dist/auto-reply/commands-registry.js +15 -18
- package/dist/auto-reply/fallback-state.js +114 -0
- package/dist/auto-reply/model-runtime.js +68 -0
- package/dist/auto-reply/reply/agent-runner-execution.js +36 -4
- package/dist/auto-reply/reply/agent-runner.js +165 -39
- package/dist/auto-reply/reply/commands-setunset-standard.js +13 -0
- package/dist/browser/config.js +26 -0
- package/dist/browser/navigation-guard.js +31 -0
- package/dist/browser/routes/agent.act.js +431 -424
- package/dist/browser/routes/agent.shared.js +47 -3
- package/dist/browser/routes/agent.snapshot.js +122 -116
- package/dist/browser/routes/agent.storage.js +303 -297
- package/dist/browser/routes/tabs.js +154 -100
- package/dist/browser/server-lifecycle.js +37 -0
- package/dist/build-info.json +3 -3
- package/dist/channels/allow-from.js +25 -0
- package/dist/channels/plugins/account-action-gate.js +13 -0
- package/dist/channels/plugins/message-actions.js +10 -0
- package/dist/channels/telegram/api.js +18 -0
- package/dist/cli/argv.js +84 -21
- package/dist/cli/banner.js +2 -1
- package/dist/cli/exec-approvals-cli.js +92 -124
- package/dist/cli/memory-cli.js +158 -61
- package/dist/cli/nodes-cli/register.push.js +63 -0
- package/dist/cli/nodes-media-utils.js +21 -0
- package/dist/cli/plugins-cli.js +245 -61
- package/dist/cli/program/build-program.js +3 -1
- package/dist/cli/program/command-registry.js +223 -136
- package/dist/cli/program/help.js +43 -12
- package/dist/cli/route.js +1 -1
- package/dist/cli/test-runtime-capture.js +24 -0
- package/dist/commands/agent.js +163 -87
- package/dist/commands/channels.mock-harness.js +23 -0
- package/dist/commands/daemon-install-runtime-warning.js +11 -0
- package/dist/commands/onboard-helpers.js +4 -4
- package/dist/commands/sessions.test-helpers.js +61 -0
- package/dist/compat/legacy-names.js +2 -2
- package/dist/config/commands.js +3 -0
- package/dist/config/config.js +1 -1
- package/dist/config/env-substitution.js +62 -34
- package/dist/config/env-vars.js +9 -0
- package/dist/config/io.js +571 -171
- package/dist/config/merge-patch.js +50 -4
- package/dist/config/redact-snapshot.js +404 -76
- package/dist/config/schema.js +58 -570
- package/dist/config/validation.js +140 -85
- package/dist/config/zod-schema.hooks.js +40 -11
- package/dist/config/zod-schema.installs.js +20 -0
- package/dist/config/zod-schema.js +8 -7
- package/dist/control-ui/assets/{index-HRr1grwl.js → index-Dvkl4Xlx.js} +2 -1
- package/dist/control-ui/assets/{index-HRr1grwl.js.map → index-Dvkl4Xlx.js.map} +1 -1
- package/dist/control-ui/index.html +1 -1
- package/dist/daemon/cmd-argv.js +21 -0
- package/dist/daemon/cmd-set.js +58 -0
- package/dist/daemon/service-types.js +1 -0
- package/dist/discord/monitor/exec-approvals.js +357 -162
- package/dist/gateway/auth.js +38 -3
- package/dist/gateway/call.js +149 -68
- package/dist/gateway/canvas-capability.js +75 -0
- package/dist/gateway/control-plane-audit.js +28 -0
- package/dist/gateway/control-plane-rate-limit.js +53 -0
- package/dist/gateway/events.js +1 -0
- package/dist/gateway/hooks.js +109 -54
- package/dist/gateway/http-common.js +22 -0
- package/dist/gateway/method-scopes.js +169 -0
- package/dist/gateway/net.js +23 -0
- package/dist/gateway/openresponses-http.js +120 -110
- package/dist/gateway/probe-auth.js +2 -0
- package/dist/gateway/protocol/index.js +3 -2
- package/dist/gateway/protocol/schema/protocol-schemas.js +2 -0
- package/dist/gateway/protocol/schema/push.js +18 -0
- package/dist/gateway/protocol/schema.js +1 -0
- package/dist/gateway/server-http.js +236 -52
- package/dist/gateway/server-methods/agent.js +162 -24
- package/dist/gateway/server-methods/chat.js +461 -130
- package/dist/gateway/server-methods/config.js +193 -150
- package/dist/gateway/server-methods/nodes.helpers.js +12 -0
- package/dist/gateway/server-methods/nodes.js +251 -69
- package/dist/gateway/server-methods/push.js +53 -0
- package/dist/gateway/server-reload-handlers.js +2 -3
- package/dist/gateway/server-runtime-config.js +5 -0
- package/dist/gateway/server-runtime-state.js +2 -0
- package/dist/gateway/server-ws-runtime.js +1 -0
- package/dist/gateway/server.impl.js +296 -139
- package/dist/gateway/session-preview.test-helpers.js +11 -0
- package/dist/gateway/startup-auth.js +126 -0
- package/dist/gateway/test-helpers.agent-results.js +15 -0
- package/dist/gateway/test-helpers.mocks.js +37 -14
- package/dist/gateway/test-helpers.server.js +161 -77
- package/dist/hooks/bundled/session-memory/handler.js +165 -34
- package/dist/hooks/gmail-watcher-lifecycle.js +23 -0
- package/dist/infra/archive-path.js +49 -0
- package/dist/infra/device-pairing.js +148 -167
- package/dist/infra/exec-approvals-allowlist.js +19 -70
- package/dist/infra/exec-approvals-analysis.js +44 -17
- package/dist/infra/exec-safe-bin-policy.js +269 -0
- package/dist/infra/fixed-window-rate-limit.js +33 -0
- package/dist/infra/git-root.js +61 -0
- package/dist/infra/heartbeat-active-hours.js +2 -2
- package/dist/infra/heartbeat-reason.js +40 -0
- package/dist/infra/heartbeat-runner.js +72 -32
- package/dist/infra/install-source-utils.js +91 -7
- package/dist/infra/node-pairing.js +50 -105
- package/dist/infra/npm-integrity.js +45 -0
- package/dist/infra/npm-pack-install.js +40 -0
- package/dist/infra/outbound/channel-adapters.js +20 -7
- package/dist/infra/outbound/message-action-runner.js +107 -327
- package/dist/infra/outbound/message.js +59 -36
- package/dist/infra/outbound/outbound-policy.js +52 -25
- package/dist/infra/outbound/outbound-send-service.js +58 -71
- package/dist/infra/pairing-files.js +10 -0
- package/dist/infra/plain-object.js +9 -0
- package/dist/infra/push-apns.js +365 -0
- package/dist/infra/restart-sentinel.js +16 -1
- package/dist/infra/restart.js +229 -26
- package/dist/infra/scp-host.js +54 -0
- package/dist/infra/update-startup.js +86 -9
- package/dist/media/inbound-path-policy.js +114 -0
- package/dist/media/input-files.js +16 -0
- package/dist/memory/test-manager.js +8 -0
- package/dist/plugin-sdk/temp-path.js +47 -0
- package/dist/plugins/discovery.js +217 -23
- package/dist/plugins/hook-runner-global.js +16 -0
- package/dist/plugins/loader.js +192 -26
- package/dist/plugins/logger.js +8 -0
- package/dist/plugins/manifest-registry.js +3 -0
- package/dist/plugins/path-safety.js +34 -0
- package/dist/plugins/registry.js +5 -2
- package/dist/plugins/runtime/index.js +271 -206
- package/dist/providers/github-copilot-models.js +4 -1
- package/dist/security/audit-channel.js +8 -19
- package/dist/security/audit-extra.async.js +354 -182
- package/dist/security/audit-extra.js +11 -1
- package/dist/security/audit-extra.sync.js +340 -33
- package/dist/security/audit-fs.js +31 -13
- package/dist/security/audit.js +145 -371
- package/dist/security/dm-policy-shared.js +24 -0
- package/dist/security/external-content.js +20 -8
- package/dist/security/fix.js +49 -85
- package/dist/security/scan-paths.js +20 -0
- package/dist/security/secret-equal.js +3 -7
- package/dist/security/windows-acl.js +30 -15
- package/dist/shared/node-list-parse.js +13 -0
- package/dist/shared/operator-scope-compat.js +37 -0
- package/dist/shared/text-chunking.js +29 -0
- package/dist/slack/blocks.test-helpers.js +31 -0
- package/dist/slack/monitor/mrkdwn.js +8 -0
- package/dist/telegram/bot-message-dispatch.js +366 -164
- package/dist/telegram/draft-stream.js +30 -7
- package/dist/telegram/reasoning-lane-coordinator.js +128 -0
- package/dist/terminal/prompt-select-styled.js +9 -0
- package/dist/test-utils/command-runner.js +6 -0
- package/dist/test-utils/internal-hook-event-payload.js +10 -0
- package/dist/test-utils/model-auth-mock.js +12 -0
- package/dist/test-utils/provider-usage-fetch.js +14 -0
- package/dist/test-utils/temp-home.js +33 -0
- package/dist/tui/components/chat-log.js +9 -0
- package/dist/tui/tui-command-handlers.js +36 -27
- package/dist/tui/tui-event-handlers.js +122 -32
- package/dist/tui/tui.js +181 -45
- package/dist/utils/mask-api-key.js +10 -0
- package/dist/utils/run-with-concurrency.js +39 -0
- package/dist/web/media.js +4 -0
- package/docs/tools/slash-commands.md +5 -1
- package/extensions/bluebubbles/package.json +1 -1
- package/extensions/copilot-proxy/package.json +1 -1
- package/extensions/diagnostics-otel/package.json +1 -1
- package/extensions/discord/package.json +1 -1
- package/extensions/feishu/package.json +1 -1
- package/extensions/feishu/src/external-keys.ts +19 -0
- package/extensions/google-antigravity-auth/package.json +1 -1
- package/extensions/google-gemini-cli-auth/package.json +1 -1
- package/extensions/googlechat/package.json +1 -1
- package/extensions/imessage/package.json +1 -1
- package/extensions/irc/package.json +1 -1
- package/extensions/line/package.json +1 -1
- package/extensions/llm-task/package.json +1 -1
- package/extensions/lobster/package.json +1 -1
- package/extensions/lobster/src/windows-spawn.ts +193 -0
- package/extensions/matrix/CHANGELOG.md +5 -0
- package/extensions/matrix/package.json +1 -1
- package/extensions/matrix/src/matrix/actions/limits.ts +6 -0
- package/extensions/mattermost/package.json +1 -1
- package/extensions/mattermost/src/mattermost/reactions.test-helpers.ts +83 -0
- package/extensions/memory-core/package.json +1 -1
- package/extensions/memory-lancedb/package.json +1 -1
- package/extensions/minimax-portal-auth/package.json +1 -1
- package/extensions/msteams/CHANGELOG.md +5 -0
- package/extensions/msteams/package.json +1 -1
- package/extensions/nextcloud-talk/package.json +1 -1
- package/extensions/nostr/CHANGELOG.md +5 -0
- package/extensions/nostr/package.json +1 -1
- package/extensions/open-prose/package.json +1 -1
- package/extensions/openai-codex-auth/package.json +1 -1
- package/extensions/signal/package.json +1 -1
- package/extensions/slack/package.json +1 -1
- package/extensions/telegram/package.json +1 -1
- package/extensions/tlon/package.json +1 -1
- package/extensions/twitch/CHANGELOG.md +5 -0
- package/extensions/twitch/package.json +1 -1
- package/extensions/voice-call/CHANGELOG.md +5 -0
- package/extensions/voice-call/package.json +1 -1
- package/extensions/whatsapp/package.json +1 -1
- package/extensions/zalo/CHANGELOG.md +5 -0
- package/extensions/zalo/package.json +1 -1
- package/extensions/zalouser/CHANGELOG.md +5 -0
- package/extensions/zalouser/package.json +1 -1
- package/package.json +1 -1
|
@@ -1,29 +1,30 @@
|
|
|
1
1
|
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../../agents/agent-scope.js";
|
|
2
|
-
import {
|
|
2
|
+
import { listChannelPlugins } from "../../channels/plugins/index.js";
|
|
3
|
+
import { CONFIG_PATH, loadConfig, parseConfigJson5, readConfigFileSnapshot, readConfigFileSnapshotForWrite, resolveConfigSnapshotHash, validateConfigObjectWithPlugins, writeConfigFile, } from "../../config/config.js";
|
|
3
4
|
import { applyLegacyMigrations } from "../../config/legacy.js";
|
|
4
5
|
import { applyMergePatch } from "../../config/merge-patch.js";
|
|
6
|
+
import { redactConfigObject, redactConfigSnapshot, restoreRedactedValues, } from "../../config/redact-snapshot.js";
|
|
5
7
|
import { buildConfigSchema } from "../../config/schema.js";
|
|
6
|
-
import {
|
|
8
|
+
import { extractDeliveryInfo } from "../../config/sessions.js";
|
|
7
9
|
import { formatDoctorNonInteractiveHint, writeRestartSentinel, } from "../../infra/restart-sentinel.js";
|
|
8
|
-
import {
|
|
10
|
+
import { scheduleGatewaySigusr1Restart } from "../../infra/restart.js";
|
|
9
11
|
import { loadPoolBotPlugins } from "../../plugins/loader.js";
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return trimmed ? trimmed : null;
|
|
17
|
-
}
|
|
12
|
+
import { diffConfigPaths } from "../config-reload.js";
|
|
13
|
+
import { formatControlPlaneActor, resolveControlPlaneActor, summarizeChangedPaths, } from "../control-plane-audit.js";
|
|
14
|
+
import { ErrorCodes, errorShape, validateConfigApplyParams, validateConfigGetParams, validateConfigPatchParams, validateConfigSchemaParams, validateConfigSetParams, } from "../protocol/index.js";
|
|
15
|
+
import { resolveBaseHashParam } from "./base-hash.js";
|
|
16
|
+
import { parseRestartRequestParams } from "./restart-request.js";
|
|
17
|
+
import { assertValidParams } from "./validation.js";
|
|
18
18
|
function requireConfigBaseHash(params, snapshot, respond) {
|
|
19
|
-
if (!snapshot.exists)
|
|
19
|
+
if (!snapshot.exists) {
|
|
20
20
|
return true;
|
|
21
|
+
}
|
|
21
22
|
const snapshotHash = resolveConfigSnapshotHash(snapshot);
|
|
22
23
|
if (!snapshotHash) {
|
|
23
24
|
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "config base hash unavailable; re-run config.get and retry"));
|
|
24
25
|
return false;
|
|
25
26
|
}
|
|
26
|
-
const baseHash =
|
|
27
|
+
const baseHash = resolveBaseHashParam(params);
|
|
27
28
|
if (!baseHash) {
|
|
28
29
|
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "config base hash required; re-run config.get and retry"));
|
|
29
30
|
return false;
|
|
@@ -34,89 +35,149 @@ function requireConfigBaseHash(params, snapshot, respond) {
|
|
|
34
35
|
}
|
|
35
36
|
return true;
|
|
36
37
|
}
|
|
38
|
+
function parseRawConfigOrRespond(params, requestName, respond) {
|
|
39
|
+
const rawValue = params.raw;
|
|
40
|
+
if (typeof rawValue !== "string") {
|
|
41
|
+
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, `invalid ${requestName} params: raw (string) required`));
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
return rawValue;
|
|
45
|
+
}
|
|
46
|
+
function parseValidateConfigFromRawOrRespond(params, requestName, snapshot, respond) {
|
|
47
|
+
const rawValue = parseRawConfigOrRespond(params, requestName, respond);
|
|
48
|
+
if (!rawValue) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const parsedRes = parseConfigJson5(rawValue);
|
|
52
|
+
if (!parsedRes.ok) {
|
|
53
|
+
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, parsedRes.error));
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
const schema = loadSchemaWithPlugins();
|
|
57
|
+
const restored = restoreRedactedValues(parsedRes.parsed, snapshot.config, schema.uiHints);
|
|
58
|
+
if (!restored.ok) {
|
|
59
|
+
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, restored.humanReadableMessage ?? "invalid config"));
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const validated = validateConfigObjectWithPlugins(restored.result);
|
|
63
|
+
if (!validated.ok) {
|
|
64
|
+
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "invalid config", {
|
|
65
|
+
details: { issues: validated.issues },
|
|
66
|
+
}));
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
return { config: validated.config, schema };
|
|
70
|
+
}
|
|
71
|
+
function resolveConfigRestartRequest(params) {
|
|
72
|
+
const { sessionKey, note, restartDelayMs } = parseRestartRequestParams(params);
|
|
73
|
+
// Extract deliveryContext + threadId for routing after restart
|
|
74
|
+
// Supports both :thread: (most channels) and :topic: (Telegram)
|
|
75
|
+
const { deliveryContext, threadId } = extractDeliveryInfo(sessionKey);
|
|
76
|
+
return {
|
|
77
|
+
sessionKey,
|
|
78
|
+
note,
|
|
79
|
+
restartDelayMs,
|
|
80
|
+
deliveryContext,
|
|
81
|
+
threadId,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function buildConfigRestartSentinelPayload(params) {
|
|
85
|
+
return {
|
|
86
|
+
kind: params.kind,
|
|
87
|
+
status: "ok",
|
|
88
|
+
ts: Date.now(),
|
|
89
|
+
sessionKey: params.sessionKey,
|
|
90
|
+
deliveryContext: params.deliveryContext,
|
|
91
|
+
threadId: params.threadId,
|
|
92
|
+
message: params.note ?? null,
|
|
93
|
+
doctorHint: formatDoctorNonInteractiveHint(),
|
|
94
|
+
stats: {
|
|
95
|
+
mode: params.mode,
|
|
96
|
+
root: CONFIG_PATH,
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
async function tryWriteRestartSentinelPayload(payload) {
|
|
101
|
+
try {
|
|
102
|
+
return await writeRestartSentinel(payload);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function loadSchemaWithPlugins() {
|
|
109
|
+
const cfg = loadConfig();
|
|
110
|
+
const workspaceDir = resolveAgentWorkspaceDir(cfg, resolveDefaultAgentId(cfg));
|
|
111
|
+
const pluginRegistry = loadPoolBotPlugins({
|
|
112
|
+
config: cfg,
|
|
113
|
+
cache: true,
|
|
114
|
+
workspaceDir,
|
|
115
|
+
logger: {
|
|
116
|
+
info: () => { },
|
|
117
|
+
warn: () => { },
|
|
118
|
+
error: () => { },
|
|
119
|
+
debug: () => { },
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
// Note: We can't easily cache this, as there are no callback that can invalidate
|
|
123
|
+
// our cache. However, both loadConfig() and loadPoolBotPlugins() already cache
|
|
124
|
+
// their results, and buildConfigSchema() is just a cheap transformation.
|
|
125
|
+
return buildConfigSchema({
|
|
126
|
+
plugins: pluginRegistry.plugins.map((plugin) => ({
|
|
127
|
+
id: plugin.id,
|
|
128
|
+
name: plugin.name,
|
|
129
|
+
description: plugin.description,
|
|
130
|
+
configUiHints: plugin.configUiHints,
|
|
131
|
+
configSchema: plugin.configJsonSchema,
|
|
132
|
+
})),
|
|
133
|
+
channels: listChannelPlugins().map((entry) => ({
|
|
134
|
+
id: entry.id,
|
|
135
|
+
label: entry.meta.label,
|
|
136
|
+
description: entry.meta.blurb,
|
|
137
|
+
configSchema: entry.configSchema?.schema,
|
|
138
|
+
configUiHints: entry.configSchema?.uiHints,
|
|
139
|
+
})),
|
|
140
|
+
});
|
|
141
|
+
}
|
|
37
142
|
export const configHandlers = {
|
|
38
143
|
"config.get": async ({ params, respond }) => {
|
|
39
|
-
if (!
|
|
40
|
-
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, `invalid config.get params: ${formatValidationErrors(validateConfigGetParams.errors)}`));
|
|
144
|
+
if (!assertValidParams(params, validateConfigGetParams, "config.get", respond)) {
|
|
41
145
|
return;
|
|
42
146
|
}
|
|
43
147
|
const snapshot = await readConfigFileSnapshot();
|
|
44
|
-
|
|
148
|
+
const schema = loadSchemaWithPlugins();
|
|
149
|
+
respond(true, redactConfigSnapshot(snapshot, schema.uiHints), undefined);
|
|
45
150
|
},
|
|
46
151
|
"config.schema": ({ params, respond }) => {
|
|
47
|
-
if (!
|
|
48
|
-
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, `invalid config.schema params: ${formatValidationErrors(validateConfigSchemaParams.errors)}`));
|
|
152
|
+
if (!assertValidParams(params, validateConfigSchemaParams, "config.schema", respond)) {
|
|
49
153
|
return;
|
|
50
154
|
}
|
|
51
|
-
|
|
52
|
-
const workspaceDir = resolveAgentWorkspaceDir(cfg, resolveDefaultAgentId(cfg));
|
|
53
|
-
const pluginRegistry = loadPoolBotPlugins({
|
|
54
|
-
config: cfg,
|
|
55
|
-
workspaceDir,
|
|
56
|
-
logger: {
|
|
57
|
-
info: () => { },
|
|
58
|
-
warn: () => { },
|
|
59
|
-
error: () => { },
|
|
60
|
-
debug: () => { },
|
|
61
|
-
},
|
|
62
|
-
});
|
|
63
|
-
const schema = buildConfigSchema({
|
|
64
|
-
plugins: pluginRegistry.plugins.map((plugin) => ({
|
|
65
|
-
id: plugin.id,
|
|
66
|
-
name: plugin.name,
|
|
67
|
-
description: plugin.description,
|
|
68
|
-
configUiHints: plugin.configUiHints,
|
|
69
|
-
configSchema: plugin.configJsonSchema,
|
|
70
|
-
})),
|
|
71
|
-
channels: listChannelPlugins().map((entry) => ({
|
|
72
|
-
id: entry.id,
|
|
73
|
-
label: entry.meta.label,
|
|
74
|
-
description: entry.meta.blurb,
|
|
75
|
-
configSchema: entry.configSchema?.schema,
|
|
76
|
-
configUiHints: entry.configSchema?.uiHints,
|
|
77
|
-
})),
|
|
78
|
-
});
|
|
79
|
-
respond(true, schema, undefined);
|
|
155
|
+
respond(true, loadSchemaWithPlugins(), undefined);
|
|
80
156
|
},
|
|
81
157
|
"config.set": async ({ params, respond }) => {
|
|
82
|
-
if (!
|
|
83
|
-
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, `invalid config.set params: ${formatValidationErrors(validateConfigSetParams.errors)}`));
|
|
158
|
+
if (!assertValidParams(params, validateConfigSetParams, "config.set", respond)) {
|
|
84
159
|
return;
|
|
85
160
|
}
|
|
86
|
-
const snapshot = await
|
|
161
|
+
const { snapshot, writeOptions } = await readConfigFileSnapshotForWrite();
|
|
87
162
|
if (!requireConfigBaseHash(params, snapshot, respond)) {
|
|
88
163
|
return;
|
|
89
164
|
}
|
|
90
|
-
const
|
|
91
|
-
if (
|
|
92
|
-
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "invalid config.set params: raw (string) required"));
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
const parsedRes = parseConfigJson5(rawValue);
|
|
96
|
-
if (!parsedRes.ok) {
|
|
97
|
-
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, parsedRes.error));
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
const validated = validateConfigObjectWithPlugins(parsedRes.parsed);
|
|
101
|
-
if (!validated.ok) {
|
|
102
|
-
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "invalid config", {
|
|
103
|
-
details: { issues: validated.issues },
|
|
104
|
-
}));
|
|
165
|
+
const parsed = parseValidateConfigFromRawOrRespond(params, "config.set", snapshot, respond);
|
|
166
|
+
if (!parsed) {
|
|
105
167
|
return;
|
|
106
168
|
}
|
|
107
|
-
await writeConfigFile(
|
|
169
|
+
await writeConfigFile(parsed.config, writeOptions);
|
|
108
170
|
respond(true, {
|
|
109
171
|
ok: true,
|
|
110
172
|
path: CONFIG_PATH,
|
|
111
|
-
config:
|
|
173
|
+
config: redactConfigObject(parsed.config, parsed.schema.uiHints),
|
|
112
174
|
}, undefined);
|
|
113
175
|
},
|
|
114
|
-
"config.patch": async ({ params, respond }) => {
|
|
115
|
-
if (!
|
|
116
|
-
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, `invalid config.patch params: ${formatValidationErrors(validateConfigPatchParams.errors)}`));
|
|
176
|
+
"config.patch": async ({ params, respond, client, context }) => {
|
|
177
|
+
if (!assertValidParams(params, validateConfigPatchParams, "config.patch", respond)) {
|
|
117
178
|
return;
|
|
118
179
|
}
|
|
119
|
-
const snapshot = await
|
|
180
|
+
const { snapshot, writeOptions } = await readConfigFileSnapshotForWrite();
|
|
120
181
|
if (!requireConfigBaseHash(params, snapshot, respond)) {
|
|
121
182
|
return;
|
|
122
183
|
}
|
|
@@ -140,9 +201,17 @@ export const configHandlers = {
|
|
|
140
201
|
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "config.patch raw must be an object"));
|
|
141
202
|
return;
|
|
142
203
|
}
|
|
143
|
-
const merged = applyMergePatch(snapshot.config, parsedRes.parsed
|
|
144
|
-
|
|
145
|
-
|
|
204
|
+
const merged = applyMergePatch(snapshot.config, parsedRes.parsed, {
|
|
205
|
+
mergeObjectArraysById: true,
|
|
206
|
+
});
|
|
207
|
+
const schemaPatch = loadSchemaWithPlugins();
|
|
208
|
+
const restoredMerge = restoreRedactedValues(merged, snapshot.config, schemaPatch.uiHints);
|
|
209
|
+
if (!restoredMerge.ok) {
|
|
210
|
+
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, restoredMerge.humanReadableMessage ?? "invalid config"));
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const migrated = applyLegacyMigrations(restoredMerge.result);
|
|
214
|
+
const resolved = migrated.next ?? restoredMerge.result;
|
|
146
215
|
const validated = validateConfigObjectWithPlugins(resolved);
|
|
147
216
|
if (!validated.ok) {
|
|
148
217
|
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "invalid config", {
|
|
@@ -150,44 +219,38 @@ export const configHandlers = {
|
|
|
150
219
|
}));
|
|
151
220
|
return;
|
|
152
221
|
}
|
|
153
|
-
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const note
|
|
158
|
-
|
|
159
|
-
:
|
|
160
|
-
|
|
161
|
-
const restartDelayMs = typeof restartDelayMsRaw === "number" && Number.isFinite(restartDelayMsRaw)
|
|
162
|
-
? Math.max(0, Math.floor(restartDelayMsRaw))
|
|
163
|
-
: undefined;
|
|
164
|
-
const payload = {
|
|
165
|
-
kind: "config-apply",
|
|
166
|
-
status: "ok",
|
|
167
|
-
ts: Date.now(),
|
|
222
|
+
const changedPaths = diffConfigPaths(snapshot.config, validated.config);
|
|
223
|
+
const actor = resolveControlPlaneActor(client);
|
|
224
|
+
context?.logGateway?.info(`config.patch write ${formatControlPlaneActor(actor)} changedPaths=${summarizeChangedPaths(changedPaths)} restartReason=config.patch`);
|
|
225
|
+
await writeConfigFile(validated.config, writeOptions);
|
|
226
|
+
const { sessionKey, note, restartDelayMs, deliveryContext, threadId } = resolveConfigRestartRequest(params);
|
|
227
|
+
const payload = buildConfigRestartSentinelPayload({
|
|
228
|
+
kind: "config-patch",
|
|
229
|
+
mode: "config.patch",
|
|
168
230
|
sessionKey,
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
231
|
+
deliveryContext,
|
|
232
|
+
threadId,
|
|
233
|
+
note,
|
|
234
|
+
});
|
|
235
|
+
const sentinelPath = await tryWriteRestartSentinelPayload(payload);
|
|
236
|
+
const audit = {
|
|
237
|
+
actor: actor.actor,
|
|
238
|
+
deviceId: actor.deviceId,
|
|
239
|
+
clientIp: actor.clientIp,
|
|
240
|
+
changedPaths,
|
|
175
241
|
};
|
|
176
|
-
let sentinelPath = null;
|
|
177
|
-
try {
|
|
178
|
-
sentinelPath = await writeRestartSentinel(payload);
|
|
179
|
-
}
|
|
180
|
-
catch {
|
|
181
|
-
sentinelPath = null;
|
|
182
|
-
}
|
|
183
242
|
const restart = scheduleGatewaySigusr1Restart({
|
|
184
243
|
delayMs: restartDelayMs,
|
|
185
244
|
reason: "config.patch",
|
|
245
|
+
audit,
|
|
186
246
|
});
|
|
247
|
+
if (restart.coalesced) {
|
|
248
|
+
context?.logGateway?.warn(`config.patch restart coalesced ${formatControlPlaneActor(actor)} delayMs=${restart.delayMs}`);
|
|
249
|
+
}
|
|
187
250
|
respond(true, {
|
|
188
251
|
ok: true,
|
|
189
252
|
path: CONFIG_PATH,
|
|
190
|
-
config: validated.config,
|
|
253
|
+
config: redactConfigObject(validated.config, schemaPatch.uiHints),
|
|
191
254
|
restart,
|
|
192
255
|
sentinel: {
|
|
193
256
|
path: sentinelPath,
|
|
@@ -195,70 +258,50 @@ export const configHandlers = {
|
|
|
195
258
|
},
|
|
196
259
|
}, undefined);
|
|
197
260
|
},
|
|
198
|
-
"config.apply": async ({ params, respond }) => {
|
|
199
|
-
if (!
|
|
200
|
-
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, `invalid config.apply params: ${formatValidationErrors(validateConfigApplyParams.errors)}`));
|
|
261
|
+
"config.apply": async ({ params, respond, client, context }) => {
|
|
262
|
+
if (!assertValidParams(params, validateConfigApplyParams, "config.apply", respond)) {
|
|
201
263
|
return;
|
|
202
264
|
}
|
|
203
|
-
const snapshot = await
|
|
265
|
+
const { snapshot, writeOptions } = await readConfigFileSnapshotForWrite();
|
|
204
266
|
if (!requireConfigBaseHash(params, snapshot, respond)) {
|
|
205
267
|
return;
|
|
206
268
|
}
|
|
207
|
-
const
|
|
208
|
-
if (
|
|
209
|
-
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "invalid config.apply params: raw (string) required"));
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
const parsedRes = parseConfigJson5(rawValue);
|
|
213
|
-
if (!parsedRes.ok) {
|
|
214
|
-
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, parsedRes.error));
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
const validated = validateConfigObjectWithPlugins(parsedRes.parsed);
|
|
218
|
-
if (!validated.ok) {
|
|
219
|
-
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "invalid config", {
|
|
220
|
-
details: { issues: validated.issues },
|
|
221
|
-
}));
|
|
269
|
+
const parsed = parseValidateConfigFromRawOrRespond(params, "config.apply", snapshot, respond);
|
|
270
|
+
if (!parsed) {
|
|
222
271
|
return;
|
|
223
272
|
}
|
|
224
|
-
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
const note
|
|
229
|
-
|
|
230
|
-
: undefined;
|
|
231
|
-
const restartDelayMsRaw = params.restartDelayMs;
|
|
232
|
-
const restartDelayMs = typeof restartDelayMsRaw === "number" && Number.isFinite(restartDelayMsRaw)
|
|
233
|
-
? Math.max(0, Math.floor(restartDelayMsRaw))
|
|
234
|
-
: undefined;
|
|
235
|
-
const payload = {
|
|
273
|
+
const changedPaths = diffConfigPaths(snapshot.config, parsed.config);
|
|
274
|
+
const actor = resolveControlPlaneActor(client);
|
|
275
|
+
context?.logGateway?.info(`config.apply write ${formatControlPlaneActor(actor)} changedPaths=${summarizeChangedPaths(changedPaths)} restartReason=config.apply`);
|
|
276
|
+
await writeConfigFile(parsed.config, writeOptions);
|
|
277
|
+
const { sessionKey, note, restartDelayMs, deliveryContext, threadId } = resolveConfigRestartRequest(params);
|
|
278
|
+
const payload = buildConfigRestartSentinelPayload({
|
|
236
279
|
kind: "config-apply",
|
|
237
|
-
|
|
238
|
-
ts: Date.now(),
|
|
280
|
+
mode: "config.apply",
|
|
239
281
|
sessionKey,
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
282
|
+
deliveryContext,
|
|
283
|
+
threadId,
|
|
284
|
+
note,
|
|
285
|
+
});
|
|
286
|
+
const sentinelPath = await tryWriteRestartSentinelPayload(payload);
|
|
287
|
+
const audit = {
|
|
288
|
+
actor: actor.actor,
|
|
289
|
+
deviceId: actor.deviceId,
|
|
290
|
+
clientIp: actor.clientIp,
|
|
291
|
+
changedPaths,
|
|
246
292
|
};
|
|
247
|
-
let sentinelPath = null;
|
|
248
|
-
try {
|
|
249
|
-
sentinelPath = await writeRestartSentinel(payload);
|
|
250
|
-
}
|
|
251
|
-
catch {
|
|
252
|
-
sentinelPath = null;
|
|
253
|
-
}
|
|
254
293
|
const restart = scheduleGatewaySigusr1Restart({
|
|
255
294
|
delayMs: restartDelayMs,
|
|
256
295
|
reason: "config.apply",
|
|
296
|
+
audit,
|
|
257
297
|
});
|
|
298
|
+
if (restart.coalesced) {
|
|
299
|
+
context?.logGateway?.warn(`config.apply restart coalesced ${formatControlPlaneActor(actor)} delayMs=${restart.delayMs}`);
|
|
300
|
+
}
|
|
258
301
|
respond(true, {
|
|
259
302
|
ok: true,
|
|
260
303
|
path: CONFIG_PATH,
|
|
261
|
-
config:
|
|
304
|
+
config: redactConfigObject(parsed.config, parsed.schema.uiHints),
|
|
262
305
|
restart,
|
|
263
306
|
sentinel: {
|
|
264
307
|
path: sentinelPath,
|
|
@@ -30,3 +30,15 @@ export function safeParseJson(value) {
|
|
|
30
30
|
return { payloadJSON: value };
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
+
export function respondUnavailableOnNodeInvokeError(respond, res) {
|
|
34
|
+
if (res.ok) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
const message = res.error && typeof res.error === "object" && "message" in res.error
|
|
38
|
+
? res.error.message
|
|
39
|
+
: null;
|
|
40
|
+
respond(false, undefined, errorShape(ErrorCodes.UNAVAILABLE, typeof message === "string" ? message : "node invoke failed", {
|
|
41
|
+
details: { nodeError: res.error ?? null },
|
|
42
|
+
}));
|
|
43
|
+
return false;
|
|
44
|
+
}
|