@poolzin/pool-bot 2026.2.0 → 2026.2.2
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 +118 -0
- package/README-header.png +0 -0
- package/dist/agents/bash-tools.exec.js +76 -25
- package/dist/agents/cli-runner/helpers.js +9 -11
- package/dist/agents/context.js +1 -1
- package/dist/agents/identity.js +47 -7
- package/dist/agents/memory-search.js +25 -8
- package/dist/agents/model-catalog.js +1 -1
- 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 +8 -10
- package/dist/agents/pi-embedded-runner/model.js +62 -3
- 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/image-tool.js +1 -1
- 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 +74 -82
- 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/mentions.js +1 -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/canvas-host/a2ui/index.html +28 -28
- 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/auth-choice.apply.oauth.js +1 -1
- 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.registry.js +1 -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/compat/legacy-names.js +2 -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/control-ui/assets/{index-CIRDm-Lu.css → index-CSfXd2LO.css} +1 -1
- package/dist/control-ui/assets/{index-CmNMuoem.js → index-HRr1grwl.js} +446 -413
- package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -0
- package/dist/control-ui/index.html +4 -4
- 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 +172 -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.mocks.js +11 -7
- 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/store.js +2 -0
- 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 +3 -3
- 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 +54 -17
- 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/dist/wizard/clack-prompter.js +9 -6
- package/extensions/googlechat/node_modules/.bin/poolbot +21 -0
- package/extensions/googlechat/package.json +2 -2
- package/extensions/line/node_modules/.bin/poolbot +21 -0
- package/extensions/line/package.json +1 -1
- package/extensions/matrix/node_modules/.bin/poolbot +21 -0
- package/extensions/matrix/package.json +1 -1
- package/extensions/memory-core/node_modules/.bin/poolbot +21 -0
- package/extensions/memory-core/package.json +4 -1
- package/extensions/twitch/node_modules/.bin/poolbot +21 -0
- package/extensions/twitch/package.json +1 -1
- package/package.json +183 -24
- package/dist/control-ui/assets/index-CmNMuoem.js.map +0 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { installSkill } from "../agents/skills-install.js";
|
|
2
2
|
import { buildWorkspaceSkillStatus } from "../agents/skills-status.js";
|
|
3
3
|
import { formatCliCommand } from "../cli/command-format.js";
|
|
4
|
+
import { normalizeSecretInput } from "../utils/normalize-secret-input.js";
|
|
4
5
|
import { detectBinary, resolveNodeManagerOptions } from "./onboard-helpers.js";
|
|
5
6
|
function summarizeInstallFailure(message) {
|
|
6
7
|
const cleaned = message.replace(/^Install failed(?:\s*\([^)]*\))?\s*:?\s*/i, "").trim();
|
|
@@ -33,14 +34,13 @@ function upsertSkillEntry(cfg, skillKey, patch) {
|
|
|
33
34
|
export async function setupSkills(cfg, workspaceDir, runtime, prompter) {
|
|
34
35
|
const report = buildWorkspaceSkillStatus(workspaceDir, { config: cfg });
|
|
35
36
|
const eligible = report.skills.filter((s) => s.eligible);
|
|
36
|
-
const
|
|
37
|
+
const unsupportedOs = report.skills.filter((s) => !s.disabled && !s.blockedByAllowlist && s.missing.os.length > 0);
|
|
38
|
+
const missing = report.skills.filter((s) => !s.eligible && !s.disabled && !s.blockedByAllowlist && s.missing.os.length === 0);
|
|
37
39
|
const blocked = report.skills.filter((s) => s.blockedByAllowlist);
|
|
38
|
-
const needsBrewPrompt = process.platform !== "win32" &&
|
|
39
|
-
report.skills.some((skill) => skill.install.some((option) => option.kind === "brew")) &&
|
|
40
|
-
!(await detectBinary("brew"));
|
|
41
40
|
await prompter.note([
|
|
42
41
|
`Eligible: ${eligible.length}`,
|
|
43
42
|
`Missing requirements: ${missing.length}`,
|
|
43
|
+
`Unsupported on this OS: ${unsupportedOs.length}`,
|
|
44
44
|
`Blocked by allowlist: ${blocked.length}`,
|
|
45
45
|
].join("\n"), "Skills status");
|
|
46
46
|
const shouldConfigure = await prompter.confirm({
|
|
@@ -49,36 +49,7 @@ export async function setupSkills(cfg, workspaceDir, runtime, prompter) {
|
|
|
49
49
|
});
|
|
50
50
|
if (!shouldConfigure)
|
|
51
51
|
return cfg;
|
|
52
|
-
|
|
53
|
-
await prompter.note([
|
|
54
|
-
"Many skill dependencies are shipped via Homebrew.",
|
|
55
|
-
"Without brew, you'll need to build from source or download releases manually.",
|
|
56
|
-
].join("\n"), "Homebrew recommended");
|
|
57
|
-
const showBrewInstall = await prompter.confirm({
|
|
58
|
-
message: "Show Homebrew install command?",
|
|
59
|
-
initialValue: true,
|
|
60
|
-
});
|
|
61
|
-
if (showBrewInstall) {
|
|
62
|
-
await prompter.note([
|
|
63
|
-
"Run:",
|
|
64
|
-
'/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"',
|
|
65
|
-
].join("\n"), "Homebrew install");
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
const nodeManager = (await prompter.select({
|
|
69
|
-
message: "Preferred node manager for skill installs",
|
|
70
|
-
options: resolveNodeManagerOptions(),
|
|
71
|
-
}));
|
|
72
|
-
let next = {
|
|
73
|
-
...cfg,
|
|
74
|
-
skills: {
|
|
75
|
-
...cfg.skills,
|
|
76
|
-
install: {
|
|
77
|
-
...cfg.skills?.install,
|
|
78
|
-
nodeManager,
|
|
79
|
-
},
|
|
80
|
-
},
|
|
81
|
-
};
|
|
52
|
+
let next = cfg;
|
|
82
53
|
const installable = missing.filter((skill) => skill.install.length > 0 && skill.missing.bins.length > 0);
|
|
83
54
|
if (installable.length > 0) {
|
|
84
55
|
const toInstall = await prompter.multiselect({
|
|
@@ -96,8 +67,50 @@ export async function setupSkills(cfg, workspaceDir, runtime, prompter) {
|
|
|
96
67
|
})),
|
|
97
68
|
],
|
|
98
69
|
});
|
|
99
|
-
const
|
|
100
|
-
|
|
70
|
+
const selectedSkills = toInstall.filter((name) => name !== "__skip__");
|
|
71
|
+
const needsBrewPrompt = process.platform !== "win32" &&
|
|
72
|
+
selectedSkills.some((name) => {
|
|
73
|
+
const skill = installable.find((s) => s.name === name);
|
|
74
|
+
return skill?.install.some((option) => option.kind === "brew");
|
|
75
|
+
}) &&
|
|
76
|
+
!(await detectBinary("brew"));
|
|
77
|
+
if (needsBrewPrompt) {
|
|
78
|
+
await prompter.note([
|
|
79
|
+
"Many skill dependencies are shipped via Homebrew.",
|
|
80
|
+
"Without brew, you'll need to build from source or download releases manually.",
|
|
81
|
+
].join("\n"), "Homebrew recommended");
|
|
82
|
+
const showBrewInstall = await prompter.confirm({
|
|
83
|
+
message: "Show Homebrew install command?",
|
|
84
|
+
initialValue: true,
|
|
85
|
+
});
|
|
86
|
+
if (showBrewInstall) {
|
|
87
|
+
await prompter.note([
|
|
88
|
+
"Run:",
|
|
89
|
+
'/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"',
|
|
90
|
+
].join("\n"), "Homebrew install");
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const needsNodeManagerPrompt = selectedSkills.some((name) => {
|
|
94
|
+
const skill = installable.find((s) => s.name === name);
|
|
95
|
+
return skill?.install.some((option) => option.kind === "node");
|
|
96
|
+
});
|
|
97
|
+
if (needsNodeManagerPrompt) {
|
|
98
|
+
const nodeManager = (await prompter.select({
|
|
99
|
+
message: "Preferred node manager for skill installs",
|
|
100
|
+
options: resolveNodeManagerOptions(),
|
|
101
|
+
}));
|
|
102
|
+
next = {
|
|
103
|
+
...next,
|
|
104
|
+
skills: {
|
|
105
|
+
...next.skills,
|
|
106
|
+
install: {
|
|
107
|
+
...next.skills?.install,
|
|
108
|
+
nodeManager,
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
for (const name of selectedSkills) {
|
|
101
114
|
const target = installable.find((s) => s.name === name);
|
|
102
115
|
if (!target || target.install.length === 0)
|
|
103
116
|
continue;
|
|
@@ -111,13 +124,25 @@ export async function setupSkills(cfg, workspaceDir, runtime, prompter) {
|
|
|
111
124
|
installId,
|
|
112
125
|
config: next,
|
|
113
126
|
});
|
|
127
|
+
const warnings = result.warnings ?? [];
|
|
114
128
|
if (result.ok) {
|
|
115
|
-
|
|
129
|
+
if (warnings.length > 0) {
|
|
130
|
+
spin.stop(`Installed ${name} (${warnings.length} warning${warnings.length === 1 ? "" : "s"})`);
|
|
131
|
+
for (const warning of warnings)
|
|
132
|
+
runtime.log(` ⚠ ${warning}`);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
spin.stop(`Installed ${name}`);
|
|
136
|
+
}
|
|
116
137
|
}
|
|
117
138
|
else {
|
|
118
139
|
const code = result.code == null ? "" : ` (exit ${result.code})`;
|
|
119
140
|
const detail = summarizeInstallFailure(result.message);
|
|
120
141
|
spin.stop(`Install failed: ${name}${code}${detail ? ` — ${detail}` : ""}`);
|
|
142
|
+
if (warnings.length > 0) {
|
|
143
|
+
for (const warning of warnings)
|
|
144
|
+
runtime.log(` ⚠ ${warning}`);
|
|
145
|
+
}
|
|
121
146
|
if (result.stderr)
|
|
122
147
|
runtime.log(result.stderr.trim());
|
|
123
148
|
else if (result.stdout)
|
|
@@ -140,7 +165,7 @@ export async function setupSkills(cfg, workspaceDir, runtime, prompter) {
|
|
|
140
165
|
message: `Enter ${skill.primaryEnv}`,
|
|
141
166
|
validate: (value) => (value?.trim() ? undefined : "Required"),
|
|
142
167
|
}));
|
|
143
|
-
next = upsertSkillEntry(next, skill.skillKey, { apiKey: apiKey
|
|
168
|
+
next = upsertSkillEntry(next, skill.skillKey, { apiKey: normalizeSecretInput(apiKey) ?? "" });
|
|
144
169
|
}
|
|
145
170
|
return next;
|
|
146
171
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ensureModelAllowlistEntry } from "./model-allowlist.js";
|
|
2
|
+
export const OPENAI_DEFAULT_MODEL = "openai/gpt-5.1-codex";
|
|
3
|
+
export function applyOpenAIProviderConfig(cfg) {
|
|
4
|
+
const next = ensureModelAllowlistEntry({
|
|
5
|
+
cfg,
|
|
6
|
+
modelRef: OPENAI_DEFAULT_MODEL,
|
|
7
|
+
});
|
|
8
|
+
const models = { ...next.agents?.defaults?.models };
|
|
9
|
+
models[OPENAI_DEFAULT_MODEL] = {
|
|
10
|
+
...models[OPENAI_DEFAULT_MODEL],
|
|
11
|
+
alias: models[OPENAI_DEFAULT_MODEL]?.alias ?? "GPT",
|
|
12
|
+
};
|
|
13
|
+
return {
|
|
14
|
+
...next,
|
|
15
|
+
agents: {
|
|
16
|
+
...next.agents,
|
|
17
|
+
defaults: {
|
|
18
|
+
...next.agents?.defaults,
|
|
19
|
+
models,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export function applyOpenAIConfig(cfg) {
|
|
25
|
+
const next = applyOpenAIProviderConfig(cfg);
|
|
26
|
+
return {
|
|
27
|
+
...next,
|
|
28
|
+
agents: {
|
|
29
|
+
...next.agents,
|
|
30
|
+
defaults: {
|
|
31
|
+
...next.agents?.defaults,
|
|
32
|
+
model: next.agents?.defaults?.model && typeof next.agents.defaults.model === "object"
|
|
33
|
+
? {
|
|
34
|
+
...next.agents.defaults.model,
|
|
35
|
+
primary: OPENAI_DEFAULT_MODEL,
|
|
36
|
+
}
|
|
37
|
+
: { primary: OPENAI_DEFAULT_MODEL },
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -3,3 +3,5 @@ export const LEGACY_MANIFEST_KEY = LEGACY_PROJECT_NAME;
|
|
|
3
3
|
export const LEGACY_PLUGIN_MANIFEST_FILENAME = `${LEGACY_PROJECT_NAME}.plugin.json`;
|
|
4
4
|
export const LEGACY_CANVAS_HANDLER_NAME = `${LEGACY_PROJECT_NAME}CanvasA2UIAction`;
|
|
5
5
|
export const LEGACY_MACOS_APP_SOURCES_DIR = "apps/macos/Sources/Pool-Bot";
|
|
6
|
+
// Alias used by conformance tests; points to the actual macOS app sources directory
|
|
7
|
+
export const MACOS_APP_SOURCES_DIR = "apps/macos/Sources/Clawdbot";
|
package/dist/config/defaults.js
CHANGED
|
@@ -5,7 +5,7 @@ import { DEFAULT_AGENT_MAX_CONCURRENT, DEFAULT_SUBAGENT_MAX_CONCURRENT } from ".
|
|
|
5
5
|
let defaultWarnState = { warned: false };
|
|
6
6
|
const DEFAULT_MODEL_ALIASES = {
|
|
7
7
|
// Anthropic (pi-ai catalog uses "latest" ids without date suffix)
|
|
8
|
-
opus: "anthropic/claude-opus-4-
|
|
8
|
+
opus: "anthropic/claude-opus-4-6",
|
|
9
9
|
sonnet: "anthropic/claude-sonnet-4-5",
|
|
10
10
|
// OpenAI
|
|
11
11
|
gpt: "openai/gpt-5.2",
|
|
@@ -145,7 +145,8 @@ export function applyModelDefaults(cfg) {
|
|
|
145
145
|
if (raw.contextWindow !== contextWindow)
|
|
146
146
|
modelMutated = true;
|
|
147
147
|
const defaultMaxTokens = Math.min(DEFAULT_MODEL_MAX_TOKENS, contextWindow);
|
|
148
|
-
const
|
|
148
|
+
const rawMaxTokens = isPositiveNumber(raw.maxTokens) ? raw.maxTokens : defaultMaxTokens;
|
|
149
|
+
const maxTokens = Math.min(rawMaxTokens, contextWindow);
|
|
149
150
|
if (raw.maxTokens !== maxTokens)
|
|
150
151
|
modelMutated = true;
|
|
151
152
|
if (!modelMutated)
|
package/dist/config/paths.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
1
2
|
import os from "node:os";
|
|
2
3
|
import path from "node:path";
|
|
4
|
+
import { expandHomePrefix, resolveRequiredHomeDir } from "../infra/home-dir.js";
|
|
3
5
|
/**
|
|
4
6
|
* Nix mode detection: When CLAWDBOT_NIX_MODE=1, the gateway is running under Nix.
|
|
5
7
|
* In this mode:
|
|
@@ -11,32 +13,73 @@ export function resolveIsNixMode(env = process.env) {
|
|
|
11
13
|
return env.CLAWDBOT_NIX_MODE === "1";
|
|
12
14
|
}
|
|
13
15
|
export const isNixMode = resolveIsNixMode();
|
|
14
|
-
const
|
|
16
|
+
const LEGACY_STATE_DIRNAMES = [".clawdbot", ".moltbot", ".moldbot"];
|
|
15
17
|
const NEW_STATE_DIRNAME = ".poolbot";
|
|
16
18
|
const CONFIG_FILENAME = "poolbot.json";
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
const LEGACY_CONFIG_FILENAMES = ["clawdbot.json", "moltbot.json", "moldbot.json"];
|
|
20
|
+
function resolveDefaultHomeDir() {
|
|
21
|
+
return resolveRequiredHomeDir(process.env, os.homedir);
|
|
19
22
|
}
|
|
20
|
-
|
|
23
|
+
/** Build a homedir thunk that respects CLAWDBOT_HOME for the given env. */
|
|
24
|
+
function envHomedir(env) {
|
|
25
|
+
return () => resolveRequiredHomeDir(env, os.homedir);
|
|
26
|
+
}
|
|
27
|
+
function legacyStateDirs(homedir = resolveDefaultHomeDir) {
|
|
28
|
+
return LEGACY_STATE_DIRNAMES.map((dir) => path.join(homedir(), dir));
|
|
29
|
+
}
|
|
30
|
+
function newStateDir(homedir = resolveDefaultHomeDir) {
|
|
21
31
|
return path.join(homedir(), NEW_STATE_DIRNAME);
|
|
22
32
|
}
|
|
33
|
+
export function resolveLegacyStateDir(homedir = resolveDefaultHomeDir) {
|
|
34
|
+
return legacyStateDirs(homedir)[0] ?? newStateDir(homedir);
|
|
35
|
+
}
|
|
36
|
+
export function resolveLegacyStateDirs(homedir = resolveDefaultHomeDir) {
|
|
37
|
+
return legacyStateDirs(homedir);
|
|
38
|
+
}
|
|
39
|
+
export function resolveNewStateDir(homedir = resolveDefaultHomeDir) {
|
|
40
|
+
return newStateDir(homedir);
|
|
41
|
+
}
|
|
23
42
|
/**
|
|
24
43
|
* State directory for mutable data (sessions, logs, caches).
|
|
25
44
|
* Can be overridden via MOLTBOT_STATE_DIR (preferred) or CLAWDBOT_STATE_DIR (legacy).
|
|
26
|
-
* Default: ~/.poolbot
|
|
45
|
+
* Default: ~/.poolbot
|
|
27
46
|
*/
|
|
28
|
-
export function resolveStateDir(env = process.env, homedir =
|
|
47
|
+
export function resolveStateDir(env = process.env, homedir = envHomedir(env)) {
|
|
48
|
+
const effectiveHomedir = () => resolveRequiredHomeDir(env, homedir);
|
|
29
49
|
const override = env.MOLTBOT_STATE_DIR?.trim() || env.CLAWDBOT_STATE_DIR?.trim();
|
|
30
|
-
if (override)
|
|
31
|
-
return resolveUserPath(override);
|
|
32
|
-
|
|
50
|
+
if (override) {
|
|
51
|
+
return resolveUserPath(override, env, effectiveHomedir);
|
|
52
|
+
}
|
|
53
|
+
const newDir = newStateDir(effectiveHomedir);
|
|
54
|
+
const legacyDirs = legacyStateDirs(effectiveHomedir);
|
|
55
|
+
const hasNew = fs.existsSync(newDir);
|
|
56
|
+
if (hasNew) {
|
|
57
|
+
return newDir;
|
|
58
|
+
}
|
|
59
|
+
const existingLegacy = legacyDirs.find((dir) => {
|
|
60
|
+
try {
|
|
61
|
+
return fs.existsSync(dir);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
if (existingLegacy) {
|
|
68
|
+
return existingLegacy;
|
|
69
|
+
}
|
|
70
|
+
return newDir;
|
|
33
71
|
}
|
|
34
|
-
function resolveUserPath(input) {
|
|
72
|
+
function resolveUserPath(input, env = process.env, homedir = envHomedir(env)) {
|
|
35
73
|
const trimmed = input.trim();
|
|
36
|
-
if (!trimmed)
|
|
74
|
+
if (!trimmed) {
|
|
37
75
|
return trimmed;
|
|
76
|
+
}
|
|
38
77
|
if (trimmed.startsWith("~")) {
|
|
39
|
-
const expanded = trimmed
|
|
78
|
+
const expanded = expandHomePrefix(trimmed, {
|
|
79
|
+
home: resolveRequiredHomeDir(env, homedir),
|
|
80
|
+
env,
|
|
81
|
+
homedir,
|
|
82
|
+
});
|
|
40
83
|
return path.resolve(expanded);
|
|
41
84
|
}
|
|
42
85
|
return path.resolve(trimmed);
|
|
@@ -47,32 +90,88 @@ export const STATE_DIR = resolveStateDir();
|
|
|
47
90
|
* Can be overridden via MOLTBOT_CONFIG_PATH (preferred) or CLAWDBOT_CONFIG_PATH (legacy).
|
|
48
91
|
* Default: ~/.poolbot/poolbot.json (or $*_STATE_DIR/poolbot.json)
|
|
49
92
|
*/
|
|
50
|
-
export function
|
|
93
|
+
export function resolveCanonicalConfigPath(env = process.env, stateDir = resolveStateDir(env, envHomedir(env))) {
|
|
51
94
|
const override = env.MOLTBOT_CONFIG_PATH?.trim() || env.CLAWDBOT_CONFIG_PATH?.trim();
|
|
52
|
-
if (override)
|
|
53
|
-
return resolveUserPath(override);
|
|
95
|
+
if (override) {
|
|
96
|
+
return resolveUserPath(override, env, envHomedir(env));
|
|
97
|
+
}
|
|
54
98
|
return path.join(stateDir, CONFIG_FILENAME);
|
|
55
99
|
}
|
|
56
|
-
export const CONFIG_PATH = resolveConfigPath();
|
|
57
100
|
/**
|
|
58
|
-
* Resolve
|
|
59
|
-
*
|
|
101
|
+
* Resolve the active config path by preferring existing config candidates
|
|
102
|
+
* before falling back to the canonical path.
|
|
60
103
|
*/
|
|
61
|
-
export function
|
|
104
|
+
export function resolveConfigPathCandidate(env = process.env, homedir = envHomedir(env)) {
|
|
105
|
+
const candidates = resolveDefaultConfigCandidates(env, homedir);
|
|
106
|
+
const existing = candidates.find((candidate) => {
|
|
107
|
+
try {
|
|
108
|
+
return fs.existsSync(candidate);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
if (existing) {
|
|
115
|
+
return existing;
|
|
116
|
+
}
|
|
117
|
+
return resolveCanonicalConfigPath(env, resolveStateDir(env, homedir));
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Active config path (prefers existing config files).
|
|
121
|
+
*/
|
|
122
|
+
export function resolveConfigPath(env = process.env, stateDir = resolveStateDir(env, envHomedir(env)), homedir = envHomedir(env)) {
|
|
123
|
+
const override = env.MOLTBOT_CONFIG_PATH?.trim() || env.CLAWDBOT_CONFIG_PATH?.trim();
|
|
124
|
+
if (override) {
|
|
125
|
+
return resolveUserPath(override, env, homedir);
|
|
126
|
+
}
|
|
127
|
+
const stateOverride = env.MOLTBOT_STATE_DIR?.trim() || env.CLAWDBOT_STATE_DIR?.trim();
|
|
128
|
+
const candidates = [
|
|
129
|
+
path.join(stateDir, CONFIG_FILENAME),
|
|
130
|
+
...LEGACY_CONFIG_FILENAMES.map((name) => path.join(stateDir, name)),
|
|
131
|
+
];
|
|
132
|
+
const existing = candidates.find((candidate) => {
|
|
133
|
+
try {
|
|
134
|
+
return fs.existsSync(candidate);
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
if (existing) {
|
|
141
|
+
return existing;
|
|
142
|
+
}
|
|
143
|
+
if (stateOverride) {
|
|
144
|
+
return path.join(stateDir, CONFIG_FILENAME);
|
|
145
|
+
}
|
|
146
|
+
const defaultStateDir = resolveStateDir(env, homedir);
|
|
147
|
+
if (path.resolve(stateDir) === path.resolve(defaultStateDir)) {
|
|
148
|
+
return resolveConfigPathCandidate(env, homedir);
|
|
149
|
+
}
|
|
150
|
+
return path.join(stateDir, CONFIG_FILENAME);
|
|
151
|
+
}
|
|
152
|
+
export const CONFIG_PATH = resolveConfigPathCandidate();
|
|
153
|
+
/**
|
|
154
|
+
* Resolve default config path candidates across default locations.
|
|
155
|
+
* Order: explicit config path → state-dir-derived paths → new default.
|
|
156
|
+
*/
|
|
157
|
+
export function resolveDefaultConfigCandidates(env = process.env, homedir = envHomedir(env)) {
|
|
158
|
+
const effectiveHomedir = () => resolveRequiredHomeDir(env, homedir);
|
|
62
159
|
const explicit = env.MOLTBOT_CONFIG_PATH?.trim() || env.CLAWDBOT_CONFIG_PATH?.trim();
|
|
63
|
-
if (explicit)
|
|
64
|
-
return [resolveUserPath(explicit)];
|
|
160
|
+
if (explicit) {
|
|
161
|
+
return [resolveUserPath(explicit, env, effectiveHomedir)];
|
|
162
|
+
}
|
|
65
163
|
const candidates = [];
|
|
66
|
-
const poolbotStateDir = env.MOLTBOT_STATE_DIR?.trim();
|
|
164
|
+
const poolbotStateDir = env.MOLTBOT_STATE_DIR?.trim() || env.CLAWDBOT_STATE_DIR?.trim();
|
|
67
165
|
if (poolbotStateDir) {
|
|
68
|
-
|
|
166
|
+
const resolved = resolveUserPath(poolbotStateDir, env, effectiveHomedir);
|
|
167
|
+
candidates.push(path.join(resolved, CONFIG_FILENAME));
|
|
168
|
+
candidates.push(...LEGACY_CONFIG_FILENAMES.map((name) => path.join(resolved, name)));
|
|
69
169
|
}
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
candidates.push(path.join(
|
|
170
|
+
const defaultDirs = [newStateDir(effectiveHomedir), ...legacyStateDirs(effectiveHomedir)];
|
|
171
|
+
for (const dir of defaultDirs) {
|
|
172
|
+
candidates.push(path.join(dir, CONFIG_FILENAME));
|
|
173
|
+
candidates.push(...LEGACY_CONFIG_FILENAMES.map((name) => path.join(dir, name)));
|
|
73
174
|
}
|
|
74
|
-
candidates.push(path.join(newStateDir(homedir), CONFIG_FILENAME));
|
|
75
|
-
candidates.push(path.join(legacyStateDir(homedir), CONFIG_FILENAME));
|
|
76
175
|
return candidates;
|
|
77
176
|
}
|
|
78
177
|
export const DEFAULT_GATEWAY_PORT = 18789;
|
|
@@ -93,28 +192,30 @@ const OAUTH_FILENAME = "oauth.json";
|
|
|
93
192
|
* Precedence:
|
|
94
193
|
* - `CLAWDBOT_OAUTH_DIR` (explicit override)
|
|
95
194
|
* - `$*_STATE_DIR/credentials` (canonical server/default)
|
|
96
|
-
* - `~/.poolbot/credentials` (legacy default)
|
|
97
195
|
*/
|
|
98
|
-
export function resolveOAuthDir(env = process.env, stateDir = resolveStateDir(env,
|
|
196
|
+
export function resolveOAuthDir(env = process.env, stateDir = resolveStateDir(env, envHomedir(env))) {
|
|
99
197
|
const override = env.CLAWDBOT_OAUTH_DIR?.trim();
|
|
100
|
-
if (override)
|
|
101
|
-
return resolveUserPath(override);
|
|
198
|
+
if (override) {
|
|
199
|
+
return resolveUserPath(override, env, envHomedir(env));
|
|
200
|
+
}
|
|
102
201
|
return path.join(stateDir, "credentials");
|
|
103
202
|
}
|
|
104
|
-
export function resolveOAuthPath(env = process.env, stateDir = resolveStateDir(env,
|
|
203
|
+
export function resolveOAuthPath(env = process.env, stateDir = resolveStateDir(env, envHomedir(env))) {
|
|
105
204
|
return path.join(resolveOAuthDir(env, stateDir), OAUTH_FILENAME);
|
|
106
205
|
}
|
|
107
206
|
export function resolveGatewayPort(cfg, env = process.env) {
|
|
108
207
|
const envRaw = env.CLAWDBOT_GATEWAY_PORT?.trim();
|
|
109
208
|
if (envRaw) {
|
|
110
209
|
const parsed = Number.parseInt(envRaw, 10);
|
|
111
|
-
if (Number.isFinite(parsed) && parsed > 0)
|
|
210
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
112
211
|
return parsed;
|
|
212
|
+
}
|
|
113
213
|
}
|
|
114
214
|
const configPort = cfg?.gateway?.port;
|
|
115
215
|
if (typeof configPort === "number" && Number.isFinite(configPort)) {
|
|
116
|
-
if (configPort > 0)
|
|
216
|
+
if (configPort > 0) {
|
|
117
217
|
return configPort;
|
|
218
|
+
}
|
|
118
219
|
}
|
|
119
220
|
return DEFAULT_GATEWAY_PORT;
|
|
120
221
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { getChatChannelMeta, listChatChannels, normalizeChatChannelId, } from "../channels/registry.js";
|
|
2
|
-
import { getChannelPluginCatalogEntry, listChannelPluginCatalogEntries, } from "../channels/plugins/catalog.js";
|
|
3
1
|
import { normalizeProviderId } from "../agents/model-selection.js";
|
|
2
|
+
import { getChannelPluginCatalogEntry, listChannelPluginCatalogEntries, } from "../channels/plugins/catalog.js";
|
|
3
|
+
import { getChatChannelMeta, listChatChannels, normalizeChatChannelId, } from "../channels/registry.js";
|
|
4
|
+
import { isRecord } from "../utils.js";
|
|
4
5
|
import { hasAnyWhatsAppAuth } from "../web/accounts.js";
|
|
5
6
|
const CHANNEL_PLUGIN_IDS = Array.from(new Set([
|
|
6
7
|
...listChatChannels().map((meta) => meta.id),
|
|
@@ -11,10 +12,8 @@ const PROVIDER_PLUGIN_IDS = [
|
|
|
11
12
|
{ pluginId: "google-gemini-cli-auth", providerId: "google-gemini-cli" },
|
|
12
13
|
{ pluginId: "qwen-portal-auth", providerId: "qwen-portal" },
|
|
13
14
|
{ pluginId: "copilot-proxy", providerId: "copilot-proxy" },
|
|
15
|
+
{ pluginId: "minimax-portal-auth", providerId: "minimax-portal" },
|
|
14
16
|
];
|
|
15
|
-
function isRecord(value) {
|
|
16
|
-
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
17
|
-
}
|
|
18
17
|
function hasNonEmptyString(value) {
|
|
19
18
|
return typeof value === "string" && value.trim().length > 0;
|
|
20
19
|
}
|
|
@@ -63,6 +62,21 @@ function isDiscordConfigured(cfg, env) {
|
|
|
63
62
|
return true;
|
|
64
63
|
return recordHasKeys(entry);
|
|
65
64
|
}
|
|
65
|
+
function isIrcConfigured(cfg, env) {
|
|
66
|
+
if (hasNonEmptyString(env.IRC_HOST) && hasNonEmptyString(env.IRC_NICK)) {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
const entry = resolveChannelConfig(cfg, "irc");
|
|
70
|
+
if (!entry)
|
|
71
|
+
return false;
|
|
72
|
+
if (hasNonEmptyString(entry.host) || hasNonEmptyString(entry.nick)) {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
if (accountsHaveKeys(entry.accounts, ["host", "nick"])) {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
return recordHasKeys(entry);
|
|
79
|
+
}
|
|
66
80
|
function isSlackConfigured(cfg, env) {
|
|
67
81
|
if (hasNonEmptyString(env.SLACK_BOT_TOKEN) ||
|
|
68
82
|
hasNonEmptyString(env.SLACK_APP_TOKEN) ||
|
|
@@ -124,6 +138,8 @@ export function isChannelConfigured(cfg, channelId, env = process.env) {
|
|
|
124
138
|
return isTelegramConfigured(cfg, env);
|
|
125
139
|
case "discord":
|
|
126
140
|
return isDiscordConfigured(cfg, env);
|
|
141
|
+
case "irc":
|
|
142
|
+
return isIrcConfigured(cfg, env);
|
|
127
143
|
case "slack":
|
|
128
144
|
return isSlackConfigured(cfg, env);
|
|
129
145
|
case "signal":
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sentinel value used to replace sensitive config fields in gateway responses.
|
|
3
|
+
* Write-side handlers (config.set, config.apply, config.patch) detect this
|
|
4
|
+
* sentinel and restore the original value from the on-disk config, so a
|
|
5
|
+
* round-trip through the Web UI does not corrupt credentials.
|
|
6
|
+
*/
|
|
7
|
+
export const REDACTED_SENTINEL = "__POOLBOT_REDACTED__";
|
|
8
|
+
/**
|
|
9
|
+
* Patterns that identify sensitive config field names.
|
|
10
|
+
* Aligned with the UI-hint logic in schema.ts.
|
|
11
|
+
*/
|
|
12
|
+
const SENSITIVE_KEY_PATTERNS = [/token/i, /password/i, /secret/i, /api.?key/i];
|
|
13
|
+
function isSensitiveKey(key) {
|
|
14
|
+
return SENSITIVE_KEY_PATTERNS.some((pattern) => pattern.test(key));
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Deep-walk an object and replace values whose key matches a sensitive pattern
|
|
18
|
+
* with the redaction sentinel.
|
|
19
|
+
*/
|
|
20
|
+
function redactObject(obj) {
|
|
21
|
+
if (obj === null || obj === undefined) {
|
|
22
|
+
return obj;
|
|
23
|
+
}
|
|
24
|
+
if (typeof obj !== "object") {
|
|
25
|
+
return obj;
|
|
26
|
+
}
|
|
27
|
+
if (Array.isArray(obj)) {
|
|
28
|
+
return obj.map(redactObject);
|
|
29
|
+
}
|
|
30
|
+
const result = {};
|
|
31
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
32
|
+
if (isSensitiveKey(key) && value !== null && value !== undefined) {
|
|
33
|
+
result[key] = REDACTED_SENTINEL;
|
|
34
|
+
}
|
|
35
|
+
else if (typeof value === "object" && value !== null) {
|
|
36
|
+
result[key] = redactObject(value);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
result[key] = value;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
export function redactConfigObject(value) {
|
|
45
|
+
return redactObject(value);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Collect all sensitive string values from a config object.
|
|
49
|
+
* Used for text-based redaction of the raw JSON5 source.
|
|
50
|
+
*/
|
|
51
|
+
function collectSensitiveValues(obj) {
|
|
52
|
+
const values = [];
|
|
53
|
+
if (obj === null || obj === undefined || typeof obj !== "object") {
|
|
54
|
+
return values;
|
|
55
|
+
}
|
|
56
|
+
if (Array.isArray(obj)) {
|
|
57
|
+
for (const item of obj) {
|
|
58
|
+
values.push(...collectSensitiveValues(item));
|
|
59
|
+
}
|
|
60
|
+
return values;
|
|
61
|
+
}
|
|
62
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
63
|
+
if (isSensitiveKey(key) && typeof value === "string" && value.length > 0) {
|
|
64
|
+
values.push(value);
|
|
65
|
+
}
|
|
66
|
+
else if (typeof value === "object" && value !== null) {
|
|
67
|
+
values.push(...collectSensitiveValues(value));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return values;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Replace known sensitive values in a raw JSON5 string with the sentinel.
|
|
74
|
+
* Values are replaced longest-first to avoid partial matches.
|
|
75
|
+
*/
|
|
76
|
+
function redactRawText(raw, config) {
|
|
77
|
+
const sensitiveValues = collectSensitiveValues(config);
|
|
78
|
+
sensitiveValues.sort((a, b) => b.length - a.length);
|
|
79
|
+
let result = raw;
|
|
80
|
+
for (const value of sensitiveValues) {
|
|
81
|
+
const escaped = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
82
|
+
result = result.replace(new RegExp(escaped, "g"), REDACTED_SENTINEL);
|
|
83
|
+
}
|
|
84
|
+
const keyValuePattern = /(^|[{\s,])((["'])([^"']+)\3|([A-Za-z0-9_$.-]+))(\s*:\s*)(["'])([^"']*)\7/g;
|
|
85
|
+
result = result.replace(keyValuePattern, (match, prefix, keyExpr, _keyQuote, keyQuoted, keyBare, sep, valQuote, val) => {
|
|
86
|
+
const key = (keyQuoted ?? keyBare);
|
|
87
|
+
if (!key || !isSensitiveKey(key)) {
|
|
88
|
+
return match;
|
|
89
|
+
}
|
|
90
|
+
if (val === REDACTED_SENTINEL) {
|
|
91
|
+
return match;
|
|
92
|
+
}
|
|
93
|
+
return `${prefix}${keyExpr}${sep}${valQuote}${REDACTED_SENTINEL}${valQuote}`;
|
|
94
|
+
});
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Returns a copy of the config snapshot with all sensitive fields
|
|
99
|
+
* replaced by {@link REDACTED_SENTINEL}. The `hash` is preserved
|
|
100
|
+
* (it tracks config identity, not content).
|
|
101
|
+
*
|
|
102
|
+
* Both `config` (the parsed object) and `raw` (the JSON5 source) are scrubbed
|
|
103
|
+
* so no credential can leak through either path.
|
|
104
|
+
*/
|
|
105
|
+
export function redactConfigSnapshot(snapshot) {
|
|
106
|
+
const redactedConfig = redactConfigObject(snapshot.config);
|
|
107
|
+
const redactedRaw = snapshot.raw ? redactRawText(snapshot.raw, snapshot.config) : null;
|
|
108
|
+
const redactedParsed = snapshot.parsed ? redactConfigObject(snapshot.parsed) : snapshot.parsed;
|
|
109
|
+
return {
|
|
110
|
+
...snapshot,
|
|
111
|
+
config: redactedConfig,
|
|
112
|
+
raw: redactedRaw,
|
|
113
|
+
parsed: redactedParsed,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Deep-walk `incoming` and replace any {@link REDACTED_SENTINEL} values
|
|
118
|
+
* (on sensitive keys) with the corresponding value from `original`.
|
|
119
|
+
*
|
|
120
|
+
* This is called by config.set / config.apply / config.patch before writing,
|
|
121
|
+
* so that credentials survive a Web UI round-trip unmodified.
|
|
122
|
+
*/
|
|
123
|
+
export function restoreRedactedValues(incoming, original) {
|
|
124
|
+
if (incoming === null || incoming === undefined) {
|
|
125
|
+
return incoming;
|
|
126
|
+
}
|
|
127
|
+
if (typeof incoming !== "object") {
|
|
128
|
+
return incoming;
|
|
129
|
+
}
|
|
130
|
+
if (Array.isArray(incoming)) {
|
|
131
|
+
const origArr = Array.isArray(original) ? original : [];
|
|
132
|
+
return incoming.map((item, i) => restoreRedactedValues(item, origArr[i]));
|
|
133
|
+
}
|
|
134
|
+
const orig = original && typeof original === "object" && !Array.isArray(original)
|
|
135
|
+
? original
|
|
136
|
+
: {};
|
|
137
|
+
const result = {};
|
|
138
|
+
for (const [key, value] of Object.entries(incoming)) {
|
|
139
|
+
if (isSensitiveKey(key) && value === REDACTED_SENTINEL) {
|
|
140
|
+
if (!(key in orig)) {
|
|
141
|
+
throw new Error(`config write rejected: "${key}" is redacted; set an explicit value instead of ${REDACTED_SENTINEL}`);
|
|
142
|
+
}
|
|
143
|
+
result[key] = orig[key];
|
|
144
|
+
}
|
|
145
|
+
else if (typeof value === "object" && value !== null) {
|
|
146
|
+
result[key] = restoreRedactedValues(value, orig[key]);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
result[key] = value;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
153
|
+
}
|