@phuetz/code-buddy 1.1.0 → 1.2.0
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/README.md +160 -165
- package/dist/agent/autonomous/agentic-coding-contract.d.ts +6 -6
- package/dist/agent/codebuddy-agent.d.ts +1 -1
- package/dist/agent/codebuddy-agent.js +69 -14
- package/dist/agent/execution/agent-executor.js +122 -12
- package/dist/agent/execution/tool-dependency-graph.js +7 -0
- package/dist/agent/execution/tool-hooks.d.ts +6 -0
- package/dist/agent/execution/tool-hooks.js +13 -5
- package/dist/agent/execution/tool-selection-strategy.d.ts +11 -2
- package/dist/agent/execution/tool-selection-strategy.js +67 -10
- package/dist/agent/extended-thinking.d.ts +8 -0
- package/dist/agent/extended-thinking.js +27 -0
- package/dist/agent/hermes-browser-backends.js +151 -11
- package/dist/agent/hermes-claw-migrate.d.ts +59 -0
- package/dist/agent/hermes-claw-migrate.js +427 -26
- package/dist/agent/hermes-parity-manifest.js +78 -71
- package/dist/agent/hermes-runtime-lifecycle.d.ts +67 -0
- package/dist/agent/hermes-runtime-lifecycle.js +468 -0
- package/dist/agent/middleware/index.d.ts +1 -0
- package/dist/agent/middleware/index.js +1 -0
- package/dist/agent/middleware/visual-validation-middleware.d.ts +17 -0
- package/dist/agent/middleware/visual-validation-middleware.js +67 -0
- package/dist/agent/model-benchmark.d.ts +77 -0
- package/dist/agent/model-benchmark.js +309 -0
- package/dist/agent/model-tier.d.ts +14 -0
- package/dist/agent/model-tier.js +71 -0
- package/dist/agent/multi-agent/agents/orchestrator-agent.js +17 -6
- package/dist/agent/multi-agent/multi-agent-system.js +3 -1
- package/dist/agent/multi-agent/session-fleet-bridge.d.ts +2 -0
- package/dist/agent/multi-agent/session-fleet-bridge.js +9 -1
- package/dist/agent/multi-agent/session-tools.js +22 -5
- package/dist/agent/reasoning/mcts.js +20 -6
- package/dist/agent/session-end-flush.d.ts +6 -1
- package/dist/agent/session-end-flush.js +20 -4
- package/dist/agent/streaming/streaming-handler.d.ts +7 -0
- package/dist/agent/streaming/streaming-handler.js +8 -1
- package/dist/agent/tool-executor.js +1 -0
- package/dist/agent/tool-handler.d.ts +5 -0
- package/dist/agent/tool-handler.js +130 -4
- package/dist/browser-automation/browser-operator-executor.js +15 -0
- package/dist/browser-automation/browser-use-runner.d.ts +96 -0
- package/dist/browser-automation/browser-use-runner.js +492 -0
- package/dist/browser-automation/camofox-runner.d.ts +107 -0
- package/dist/browser-automation/camofox-runner.js +287 -0
- package/dist/channels/dingtalk/index.js +3 -0
- package/dist/channels/discord/client.d.ts +9 -0
- package/dist/channels/discord/client.js +12 -0
- package/dist/channels/feishu/index.d.ts +167 -1
- package/dist/channels/feishu/index.js +432 -7
- package/dist/channels/gateway-lifecycle.d.ts +132 -0
- package/dist/channels/gateway-lifecycle.js +219 -0
- package/dist/channels/google-chat/index.d.ts +5 -1
- package/dist/channels/google-chat/index.js +5 -1
- package/dist/channels/imessage/index.d.ts +14 -0
- package/dist/channels/imessage/index.js +64 -17
- package/dist/channels/index.d.ts +4 -0
- package/dist/channels/index.js +4 -0
- package/dist/channels/irc/index.d.ts +73 -3
- package/dist/channels/irc/index.js +446 -11
- package/dist/channels/line/index.js +3 -0
- package/dist/channels/mattermost/index.d.ts +51 -4
- package/dist/channels/mattermost/index.js +303 -20
- package/dist/channels/nextcloud-talk/index.d.ts +89 -8
- package/dist/channels/nextcloud-talk/index.js +367 -16
- package/dist/channels/nostr/index.d.ts +121 -1
- package/dist/channels/nostr/index.js +396 -8
- package/dist/channels/ntfy/index.js +3 -0
- package/dist/channels/qq/index.js +3 -0
- package/dist/channels/slash-parity.d.ts +117 -0
- package/dist/channels/slash-parity.js +185 -0
- package/dist/channels/synology-chat/index.js +3 -0
- package/dist/channels/teams/index.d.ts +3 -0
- package/dist/channels/teams/index.js +3 -0
- package/dist/channels/telegram/client.d.ts +9 -0
- package/dist/channels/telegram/client.js +12 -0
- package/dist/channels/twilio-voice/index.js +3 -0
- package/dist/channels/webchat/index.d.ts +7 -1
- package/dist/channels/webchat/index.js +7 -1
- package/dist/channels/wecom/index.js +3 -0
- package/dist/channels/weixin/index.js +3 -0
- package/dist/channels/zalo/index.js +3 -0
- package/dist/cli/config-loader.js +1 -1
- package/dist/codebuddy/client.d.ts +30 -1
- package/dist/codebuddy/client.js +167 -20
- package/dist/codebuddy/providers/provider-chatgpt-responses.d.ts +5 -0
- package/dist/codebuddy/providers/provider-chatgpt-responses.js +1 -0
- package/dist/codebuddy/providers/provider-gemini-native.js +1 -1
- package/dist/codebuddy/providers/provider-openai-compat.d.ts +5 -0
- package/dist/codebuddy/providers/provider-openai-compat.js +82 -2
- package/dist/codebuddy/tool-definitions/computer-control-tools.js +32 -2
- package/dist/codebuddy/tool-definitions/core-tools.js +3 -3
- package/dist/codebuddy/tool-definitions/index.d.ts +2 -1
- package/dist/codebuddy/tool-definitions/index.js +2 -1
- package/dist/codebuddy/tool-definitions/multimodal-tools.d.ts +1 -0
- package/dist/codebuddy/tool-definitions/multimodal-tools.js +44 -1
- package/dist/codebuddy/tool-definitions/windows-tools.d.ts +2 -0
- package/dist/codebuddy/tool-definitions/windows-tools.js +34 -0
- package/dist/codebuddy/tools.d.ts +1 -1
- package/dist/codebuddy/tools.js +27 -24
- package/dist/commands/cli/hermes-commands.js +108 -0
- package/dist/commands/cli/native-engine-commands.js +102 -3
- package/dist/commands/cli/utility-commands.js +45 -0
- package/dist/commands/enhanced-command-handler.js +1 -1
- package/dist/commands/goal-cli.d.ts +30 -0
- package/dist/commands/goal-cli.js +210 -27
- package/dist/commands/handlers/goal-handler.d.ts +9 -2
- package/dist/commands/handlers/goal-handler.js +36 -9
- package/dist/commands/handlers/infra-handlers.js +36 -0
- package/dist/commands/handlers/memory-handlers.js +96 -1
- package/dist/commands/headless-slash.d.ts +2 -0
- package/dist/commands/headless-slash.js +12 -0
- package/dist/commands/llm-provider-resolution.d.ts +5 -3
- package/dist/commands/llm-provider-resolution.js +87 -33
- package/dist/commands/ollama.d.ts +25 -0
- package/dist/commands/ollama.js +100 -0
- package/dist/commands/provider.d.ts +5 -0
- package/dist/commands/provider.js +106 -55
- package/dist/commands/spec-next.js +2 -1
- package/dist/commands/spec-plan.js +5 -16
- package/dist/commands/tunnel.d.ts +2 -0
- package/dist/commands/tunnel.js +48 -0
- package/dist/config/config-resolver.d.ts +2 -1
- package/dist/config/config-resolver.js +54 -41
- package/dist/config/constants.d.ts +28 -0
- package/dist/config/constants.js +7 -0
- package/dist/config/env-schema.js +756 -0
- package/dist/config/model-tools.js +4 -4
- package/dist/config/toml-config.d.ts +16 -0
- package/dist/config/toml-config.js +3 -0
- package/dist/context/context-manager-v2.js +1 -0
- package/dist/daemon/autonomous-daemon.d.ts +1 -1
- package/dist/daemon/autonomous-daemon.js +3 -3
- package/dist/daemon/autonomous-loop.js +11 -2
- package/dist/daemon/colab-goal.js +9 -1
- package/dist/daemon/cron-agent-bridge.d.ts +12 -3
- package/dist/daemon/cron-agent-bridge.js +25 -9
- package/dist/desktop/codebuddy-engine-adapter.d.ts +32 -0
- package/dist/desktop/codebuddy-engine-adapter.js +257 -80
- package/dist/desktop/engine-adapter.d.ts +14 -0
- package/dist/desktop-automation/automation-manager.js +16 -0
- package/dist/desktop-automation/omniparser-runner.d.ts +63 -0
- package/dist/desktop-automation/omniparser-runner.js +115 -0
- package/dist/desktop-automation/smart-snapshot.d.ts +11 -0
- package/dist/desktop-automation/smart-snapshot.js +72 -5
- package/dist/doctor/index.js +40 -23
- package/dist/events/types.d.ts +11 -0
- package/dist/fleet/colab-store.js +14 -2
- package/dist/fleet/peer-session-bridge.js +50 -12
- package/dist/fleet/peer-tool-bridge.js +14 -0
- package/dist/goals/goal-decomposer.d.ts +27 -0
- package/dist/goals/goal-decomposer.js +293 -0
- package/dist/goals/goal-judge-client.d.ts +15 -0
- package/dist/goals/goal-judge-client.js +45 -0
- package/dist/goals/goal-judge.d.ts +2 -0
- package/dist/goals/goal-judge.js +35 -6
- package/dist/goals/goal-loop.d.ts +14 -0
- package/dist/goals/goal-loop.js +47 -2
- package/dist/goals/goal-manager.d.ts +6 -0
- package/dist/goals/goal-manager.js +67 -14
- package/dist/goals/goal-state.d.ts +13 -0
- package/dist/goals/goal-state.js +83 -13
- package/dist/goals/goal-store.d.ts +3 -0
- package/dist/goals/goal-store.js +31 -15
- package/dist/hooks/user-hooks.js +17 -3
- package/dist/index.js +464 -25
- package/dist/input/text-to-speech.d.ts +2 -6
- package/dist/input/text-to-speech.js +2 -27
- package/dist/integrations/screenpipe/screenpipe-client.d.ts +10 -0
- package/dist/integrations/screenpipe/screenpipe-client.js +12 -2
- package/dist/integrations/tailscale.d.ts +13 -0
- package/dist/integrations/tailscale.js +87 -34
- package/dist/knowledge/workspace-indexer.js +53 -9
- package/dist/mcp/client.js +1 -0
- package/dist/mcp/mcp-desktop-tools.d.ts +23 -0
- package/dist/mcp/mcp-desktop-tools.js +158 -0
- package/dist/mcp/mcp-server.js +2 -0
- package/dist/memory/enhanced-memory.d.ts +10 -0
- package/dist/memory/enhanced-memory.js +33 -2
- package/dist/memory/index.d.ts +3 -1
- package/dist/memory/index.js +4 -1
- package/dist/memory/memory-auto-proposer.d.ts +23 -0
- package/dist/memory/memory-auto-proposer.js +308 -0
- package/dist/memory/memory-candidate-queue.d.ts +92 -0
- package/dist/memory/memory-candidate-queue.js +261 -0
- package/dist/memory/persistent-memory.d.ts +45 -2
- package/dist/memory/persistent-memory.js +236 -40
- package/dist/ml/bayesian-qualifier.d.ts +1 -1
- package/dist/ml/bayesian-qualifier.js +35 -2
- package/dist/openclaw/gateway-bridge.js +5 -0
- package/dist/plugins/marketplace.d.ts +1 -0
- package/dist/plugins/marketplace.js +7 -0
- package/dist/prompts/execution-discipline.d.ts +14 -0
- package/dist/prompts/execution-discipline.js +29 -0
- package/dist/prompts/variation-injector.js +9 -2
- package/dist/protocols/acp/acp-agentic-runner.d.ts +7 -6
- package/dist/protocols/acp/acp-agentic-runner.js +115 -18
- package/dist/protocols/acp/acp-session-store.d.ts +23 -0
- package/dist/protocols/acp/acp-session-store.js +77 -0
- package/dist/protocols/acp/acp-stdio-server.d.ts +7 -0
- package/dist/protocols/acp/acp-stdio-server.js +71 -16
- package/dist/providers/active-llm-registry.d.ts +37 -0
- package/dist/providers/active-llm-registry.js +186 -0
- package/dist/providers/auxiliary-provider.d.ts +25 -0
- package/dist/providers/auxiliary-provider.js +192 -0
- package/dist/providers/codex-oauth.d.ts +1 -1
- package/dist/providers/codex-oauth.js +27 -4
- package/dist/providers/index.d.ts +3 -0
- package/dist/providers/index.js +4 -0
- package/dist/providers/provider-catalog.d.ts +70 -0
- package/dist/providers/provider-catalog.js +738 -0
- package/dist/providers/provider-fallback.d.ts +35 -0
- package/dist/providers/provider-fallback.js +171 -0
- package/dist/providers/xai-oauth.d.ts +128 -0
- package/dist/providers/xai-oauth.js +735 -0
- package/dist/scheduler/cron-scheduler.d.ts +15 -3
- package/dist/scheduler/cron-scheduler.js +53 -7
- package/dist/scheduler/script-runner.d.ts +2 -0
- package/dist/scheduler/script-runner.js +19 -4
- package/dist/scheduler/watchdog-handlers.js +7 -20
- package/dist/search/usearch-index.js +7 -2
- package/dist/security/tool-policy/tool-groups.js +2 -0
- package/dist/server/index.js +17 -2
- package/dist/server/routes/mobile.d.ts +12 -4
- package/dist/server/routes/mobile.js +116 -1
- package/dist/server/tls-config.d.ts +35 -0
- package/dist/server/tls-config.js +142 -0
- package/dist/server/tunnel-manager.d.ts +20 -0
- package/dist/server/tunnel-manager.js +58 -0
- package/dist/server/websocket/fleet-bridge.js +5 -0
- package/dist/services/prompt-builder.d.ts +1 -0
- package/dist/services/prompt-builder.js +49 -8
- package/dist/shared/engine-types.d.ts +15 -1
- package/dist/sidecar/sidecar-bridge.d.ts +1 -0
- package/dist/sidecar/sidecar-bridge.js +21 -10
- package/dist/spec/spec-store.js +8 -1
- package/dist/talk-mode/providers/audioreader-tts.d.ts +1 -1
- package/dist/talk-mode/providers/audioreader-tts.js +8 -8
- package/dist/templates/project-scaffolding.js +3 -2
- package/dist/themes/theme-schema.d.ts +10 -10
- package/dist/tools/application-profiles.js +38 -0
- package/dist/tools/bash/bash-tool.d.ts +1 -0
- package/dist/tools/bash/bash-tool.js +16 -2
- package/dist/tools/bash/command-validator.js +3 -0
- package/dist/tools/bash/rtk-rewrite.d.ts +16 -0
- package/dist/tools/bash/rtk-rewrite.js +100 -0
- package/dist/tools/bash/streaming-executor.js +14 -1
- package/dist/tools/computer-control-tool.d.ts +16 -1
- package/dist/tools/computer-control-tool.js +317 -5
- package/dist/tools/document-generator.d.ts +14 -0
- package/dist/tools/document-generator.js +79 -0
- package/dist/tools/execute-code-rpc-invoker.d.ts +26 -2
- package/dist/tools/execute-code-rpc-invoker.js +46 -5
- package/dist/tools/metadata.js +28 -0
- package/dist/tools/office-macro-tool.d.ts +10 -0
- package/dist/tools/office-macro-tool.js +93 -0
- package/dist/tools/registry/index.d.ts +2 -1
- package/dist/tools/registry/index.js +2 -1
- package/dist/tools/registry/memory-tools.d.ts +20 -0
- package/dist/tools/registry/memory-tools.js +225 -3
- package/dist/tools/registry/multimodal-tools.js +2 -2
- package/dist/tools/registry/vision-tools.d.ts +33 -0
- package/dist/tools/registry/vision-tools.js +308 -6
- package/dist/tools/registry/windows-tools.d.ts +2 -0
- package/dist/tools/registry/windows-tools.js +47 -0
- package/dist/tools/route-peer-tool.js +14 -0
- package/dist/tools/screenshot-tool.js +14 -2
- package/dist/tools/text-to-speech-tool.d.ts +1 -1
- package/dist/tools/text-to-speech-tool.js +2 -12
- package/dist/tools/tool-selector.js +3 -59
- package/dist/utils/config-validation/schema.d.ts +40 -7
- package/dist/utils/config-validation/schema.js +130 -1
- package/dist/utils/cost-tracker.js +21 -1
- package/dist/utils/disk-guard.d.ts +180 -0
- package/dist/utils/disk-guard.js +404 -0
- package/dist/utils/interactive-setup.js +35 -2
- package/dist/utils/model-utils.js +11 -2
- package/dist/utils/output-sanitizer.js +11 -0
- package/dist/utils/provider-detector.d.ts +8 -7
- package/dist/utils/provider-detector.js +19 -77
- package/dist/utils/settings-manager.d.ts +7 -0
- package/dist/utils/token-display.d.ts +3 -3
- package/dist/utils/token-display.js +22 -4
- package/dist/wizard/onboarding.d.ts +39 -0
- package/dist/wizard/onboarding.js +433 -21
- package/dist/wizard/provider-onboarding.d.ts +8 -23
- package/dist/wizard/provider-onboarding.js +39 -158
- package/package.json +4 -2
- package/dist/plugins/provider-onboarding.d.ts +0 -23
- package/dist/plugins/provider-onboarding.js +0 -116
- package/dist/utils/kokoro-tts.d.ts +0 -8
- package/dist/utils/kokoro-tts.js +0 -67
|
@@ -25,15 +25,13 @@ import path from 'path';
|
|
|
25
25
|
import { getSkillsHub } from '../skills/hub.js';
|
|
26
26
|
const HOME_CANDIDATES = ['.openclaw', '.clawdbot', '.moltbot'];
|
|
27
27
|
const CONFIG_NAMES = ['clawdbot.json', 'moltbot.json', 'openclaw.json', 'config.json'];
|
|
28
|
-
|
|
28
|
+
// `plugin-skills` is the OpenClaw 2026.6.x location; each entry there is usually
|
|
29
|
+
// a *symlink* into the installed openclaw npm package (verified against a real
|
|
30
|
+
// install). `discoverSkillDirs` therefore follows symlinks via `statSync`.
|
|
31
|
+
const SKILL_DIR_CANDIDATES = ['skills', 'plugin-skills', 'agent/skills', '.skills', 'skill'];
|
|
29
32
|
const COMMAND_DIR_CANDIDATES = ['commands', 'slash-commands', 'agent/commands', '.commands'];
|
|
30
33
|
// Categories that `--preset user-data` keeps as imports (user content only).
|
|
31
34
|
const USER_DATA_IMPORTS = new Set(['persona', 'memory', 'user', 'agents', 'skills', 'commands']);
|
|
32
|
-
/**
|
|
33
|
-
* Archive categories whose slice can embed credentials. Their review files are
|
|
34
|
-
* chmod 0600 like `secrets.json` (best-effort; no-op where chmod is unsupported).
|
|
35
|
-
*/
|
|
36
|
-
const SENSITIVE_ARCHIVE_CATEGORIES = new Set(['hooks', 'webhooks', 'portal', 'secrets', 'custom_providers']);
|
|
37
35
|
/**
|
|
38
36
|
* Dev-only invariant: every config key is claimed by at most one archive spec.
|
|
39
37
|
* Two specs sharing a key would archive the same slice into two files. Throws
|
|
@@ -193,7 +191,160 @@ export function mapClawAgentBehavior(cfg) {
|
|
|
193
191
|
}
|
|
194
192
|
return out;
|
|
195
193
|
}
|
|
196
|
-
/**
|
|
194
|
+
/**
|
|
195
|
+
* Map OpenClaw cron config to a list of CronJob-compatible entries.
|
|
196
|
+
*
|
|
197
|
+
* OpenClaw stores cron jobs as an array of `{ schedule, task, label? }` objects
|
|
198
|
+
* or — in 2026.6.x — under `scheduler.jobs`. Only entries with both a valid
|
|
199
|
+
* 5-field cron expression and a non-empty task string are mapped. Everything
|
|
200
|
+
* else is silently dropped (will remain in the archived raw slice if one
|
|
201
|
+
* exists).
|
|
202
|
+
*/
|
|
203
|
+
export function mapClawCronJobs(cfg) {
|
|
204
|
+
const out = [];
|
|
205
|
+
const candidates = [
|
|
206
|
+
cfg.cronJobs, cfg.cron, cfg.cronjobs,
|
|
207
|
+
nestedValue(cfg, 'scheduler.jobs'),
|
|
208
|
+
];
|
|
209
|
+
for (const raw of candidates) {
|
|
210
|
+
if (!Array.isArray(raw))
|
|
211
|
+
continue;
|
|
212
|
+
for (const item of raw) {
|
|
213
|
+
if (!item || typeof item !== 'object')
|
|
214
|
+
continue;
|
|
215
|
+
const rec = item;
|
|
216
|
+
const schedule = typeof rec.schedule === 'string' ? rec.schedule.trim() : undefined;
|
|
217
|
+
const task = typeof rec.task === 'string' ? rec.task.trim() : undefined;
|
|
218
|
+
if (!schedule || !task)
|
|
219
|
+
continue;
|
|
220
|
+
// Basic 5-field cron validation: must have exactly 5 space-separated fields.
|
|
221
|
+
if (schedule.split(/\s+/).length !== 5)
|
|
222
|
+
continue;
|
|
223
|
+
const name = typeof rec.label === 'string' && rec.label.trim()
|
|
224
|
+
? rec.label.trim()
|
|
225
|
+
: typeof rec.name === 'string' && rec.name.trim()
|
|
226
|
+
? rec.name.trim()
|
|
227
|
+
: `claw-cron-${out.length}`;
|
|
228
|
+
out.push({ name, schedule, task });
|
|
229
|
+
}
|
|
230
|
+
if (out.length > 0)
|
|
231
|
+
break; // first populated candidate wins
|
|
232
|
+
}
|
|
233
|
+
return out;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Map OpenClaw command allowlist into a flat string array of allowed command
|
|
237
|
+
* basenames / patterns, consumable by Code Buddy's command validator.
|
|
238
|
+
*
|
|
239
|
+
* OpenClaw stores these as a flat string array under `commandAllowlist`,
|
|
240
|
+
* `allowlist`, or `allowedCommands`. Items that look like credentials or paths
|
|
241
|
+
* outside of simple command names are filtered out.
|
|
242
|
+
*/
|
|
243
|
+
export function mapClawCommandAllowlist(cfg) {
|
|
244
|
+
const candidates = [cfg.commandAllowlist, cfg.allowlist, cfg.allowedCommands];
|
|
245
|
+
for (const raw of candidates) {
|
|
246
|
+
if (!Array.isArray(raw))
|
|
247
|
+
continue;
|
|
248
|
+
const out = [];
|
|
249
|
+
for (const item of raw) {
|
|
250
|
+
if (typeof item !== 'string')
|
|
251
|
+
continue;
|
|
252
|
+
const trimmed = item.trim();
|
|
253
|
+
// Skip empty, absolute paths, and anything resembling a secret.
|
|
254
|
+
if (!trimmed)
|
|
255
|
+
continue;
|
|
256
|
+
if (trimmed.startsWith('/') || trimmed.startsWith('~'))
|
|
257
|
+
continue;
|
|
258
|
+
if (/(token|secret|key|password)/i.test(trimmed))
|
|
259
|
+
continue;
|
|
260
|
+
out.push(trimmed);
|
|
261
|
+
}
|
|
262
|
+
if (out.length > 0)
|
|
263
|
+
return out;
|
|
264
|
+
}
|
|
265
|
+
return [];
|
|
266
|
+
}
|
|
267
|
+
// Known memory provider names that Code Buddy supports.
|
|
268
|
+
const KNOWN_MEMORY_PROVIDERS = new Set(['local', 'honcho', 'mem0', 'redis', 'sqlite', 'json', 'markdown']);
|
|
269
|
+
/**
|
|
270
|
+
* Map OpenClaw memory backend config to a Code Buddy memory provider name.
|
|
271
|
+
*
|
|
272
|
+
* Only well-known provider names are accepted; unknown strings are left
|
|
273
|
+
* unmapped (will stay in the archive). Credentials (API keys, URLs) in the
|
|
274
|
+
* memory backend config are never imported.
|
|
275
|
+
*/
|
|
276
|
+
export function mapClawMemoryBackend(cfg) {
|
|
277
|
+
const raw = firstDefined(cfg, [
|
|
278
|
+
'memoryBackend', 'memory_backend', 'memory.provider', 'memory.backend',
|
|
279
|
+
]);
|
|
280
|
+
// Could be a string name or an object { provider: "...", ... }.
|
|
281
|
+
let name;
|
|
282
|
+
if (typeof raw === 'string') {
|
|
283
|
+
name = raw.trim().toLowerCase();
|
|
284
|
+
}
|
|
285
|
+
else if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
|
|
286
|
+
const rec = raw;
|
|
287
|
+
const p = rec.provider ?? rec.backend ?? rec.type;
|
|
288
|
+
if (typeof p === 'string')
|
|
289
|
+
name = p.trim().toLowerCase();
|
|
290
|
+
}
|
|
291
|
+
if (name && KNOWN_MEMORY_PROVIDERS.has(name))
|
|
292
|
+
return name;
|
|
293
|
+
return undefined;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Map OpenClaw exec timeout to a numeric millisecond value for Code Buddy's
|
|
297
|
+
* `execTimeout` setting.
|
|
298
|
+
*
|
|
299
|
+
* OpenClaw stores this as seconds (integer) or milliseconds (large integer).
|
|
300
|
+
* Values ≤ 0 are ignored. Values that look like seconds (≤ 3600) are
|
|
301
|
+
* multiplied by 1000. Clamped to a maximum of 3600000 ms (1 hour).
|
|
302
|
+
*/
|
|
303
|
+
export function mapClawExecTimeout(cfg) {
|
|
304
|
+
const raw = firstDefined(cfg, [
|
|
305
|
+
'execTimeout', 'exec_timeout', 'timeout',
|
|
306
|
+
'agents.defaults.execTimeout', 'agent.execTimeout',
|
|
307
|
+
]);
|
|
308
|
+
if (typeof raw !== 'number' || raw <= 0 || !Number.isFinite(raw))
|
|
309
|
+
return undefined;
|
|
310
|
+
// Heuristic: values ≤ 3600 are likely seconds; larger ones milliseconds.
|
|
311
|
+
const ms = raw <= 3600 ? Math.round(raw * 1000) : Math.round(raw);
|
|
312
|
+
return Math.min(ms, 3_600_000);
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Map OpenClaw vision settings to Code Buddy's vision config keys.
|
|
316
|
+
*
|
|
317
|
+
* Only the `enabled` boolean and `model` string are imported. Provider
|
|
318
|
+
* credentials, endpoint URLs, and other complex fields are left archived.
|
|
319
|
+
*/
|
|
320
|
+
export function mapClawVisionSettings(cfg) {
|
|
321
|
+
const out = {};
|
|
322
|
+
const block = firstDefined(cfg, ['vision', 'visionSettings']);
|
|
323
|
+
if (block && typeof block === 'object' && !Array.isArray(block)) {
|
|
324
|
+
const rec = block;
|
|
325
|
+
if (typeof rec.enabled === 'boolean')
|
|
326
|
+
out.visionEnabled = rec.enabled;
|
|
327
|
+
if (typeof rec.model === 'string' && rec.model.trim()) {
|
|
328
|
+
// Only import model names, not URLs or credentials.
|
|
329
|
+
const model = rec.model.trim();
|
|
330
|
+
if (!model.startsWith('http') && !/(token|secret|key)/i.test(model)) {
|
|
331
|
+
out.visionModel = model;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return out;
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Discover OpenClaw skill directories: each subdir containing a `SKILL.md`.
|
|
339
|
+
*
|
|
340
|
+
* Entries may be plain dirs (legacy `skills/<name>/SKILL.md`) or **symlinks** to
|
|
341
|
+
* a skill dir inside the installed openclaw npm package (2026.6.x
|
|
342
|
+
* `plugin-skills/<name>` → `.../dist/extensions/.../<name>`). A Dirent for a
|
|
343
|
+
* symlink reports `isDirectory() === false`, so we resolve the link with
|
|
344
|
+
* `statSync` (in a try/catch, to survive a dangling link when openclaw has been
|
|
345
|
+
* uninstalled) and accept any entry that resolves to a directory holding a
|
|
346
|
+
* readable `SKILL.md`.
|
|
347
|
+
*/
|
|
197
348
|
function discoverSkillDirs(home) {
|
|
198
349
|
const found = [];
|
|
199
350
|
const seen = new Set();
|
|
@@ -202,7 +353,17 @@ function discoverSkillDirs(home) {
|
|
|
202
353
|
if (!fs.existsSync(root) || !fs.statSync(root).isDirectory())
|
|
203
354
|
continue;
|
|
204
355
|
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
|
|
205
|
-
|
|
356
|
+
// Resolve symlinks (plugin-skills entries are links into the npm package).
|
|
357
|
+
let isDir = entry.isDirectory();
|
|
358
|
+
if (!isDir && entry.isSymbolicLink()) {
|
|
359
|
+
try {
|
|
360
|
+
isDir = fs.statSync(path.join(root, entry.name)).isDirectory();
|
|
361
|
+
}
|
|
362
|
+
catch {
|
|
363
|
+
continue; // dangling symlink (openclaw uninstalled) — skip.
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
if (!isDir)
|
|
206
367
|
continue;
|
|
207
368
|
const skillFile = path.join(root, entry.name, 'SKILL.md');
|
|
208
369
|
const safeName = entry.name.replace(/[^a-zA-Z0-9_-]/g, '-');
|
|
@@ -214,6 +375,46 @@ function discoverSkillDirs(home) {
|
|
|
214
375
|
}
|
|
215
376
|
return found;
|
|
216
377
|
}
|
|
378
|
+
/**
|
|
379
|
+
* Resolve the base directories that may hold OpenClaw identity / memory markdown
|
|
380
|
+
* files. OpenClaw 2026.6.x stores SOUL/USER/AGENTS/… under the configured
|
|
381
|
+
* **workspace** dir (`agents.defaults.workspace`, real value
|
|
382
|
+
* `~/.openclaw/workspace`), not at the home root used by legacy clawdbot/moltbot
|
|
383
|
+
* installs. Both are searched (home root first for back-compat, then the
|
|
384
|
+
* configured workspace, then the conventional `home/workspace` fallback);
|
|
385
|
+
* callers dedup per category so an entry is emitted once. Only directories under
|
|
386
|
+
* (or equal to) `home` are accepted — an absolute `workspace` pointing outside
|
|
387
|
+
* the OpenClaw home is ignored to avoid reading arbitrary user paths.
|
|
388
|
+
*/
|
|
389
|
+
function identityBaseDirs(home, cfg) {
|
|
390
|
+
const dirs = [home];
|
|
391
|
+
const configured = firstStringPath(cfg, [
|
|
392
|
+
'agents.defaults.workspace', 'agent.workspace', 'workspace',
|
|
393
|
+
]);
|
|
394
|
+
const candidates = [configured, path.join(home, 'workspace')];
|
|
395
|
+
for (const cand of candidates) {
|
|
396
|
+
if (!cand)
|
|
397
|
+
continue;
|
|
398
|
+
const resolved = path.resolve(home, cand);
|
|
399
|
+
// Only trust workspace dirs inside the OpenClaw home (the real layout). An
|
|
400
|
+
// out-of-tree absolute path is left to the archive/manual path, never read.
|
|
401
|
+
const rel = path.relative(home, resolved);
|
|
402
|
+
const insideHome = resolved === home || (!rel.startsWith('..') && !path.isAbsolute(rel));
|
|
403
|
+
if (insideHome && !dirs.includes(resolved) && fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) {
|
|
404
|
+
dirs.push(resolved);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return dirs;
|
|
408
|
+
}
|
|
409
|
+
/** First existing path for `name` across the identity base dirs (or null). */
|
|
410
|
+
function findIdentityFile(bases, name) {
|
|
411
|
+
for (const base of bases) {
|
|
412
|
+
const full = path.join(base, name);
|
|
413
|
+
if (fs.existsSync(full) && fs.statSync(full).isFile())
|
|
414
|
+
return full;
|
|
415
|
+
}
|
|
416
|
+
return null;
|
|
417
|
+
}
|
|
217
418
|
/** Discover OpenClaw custom slash command files consumable by Code Buddy. */
|
|
218
419
|
function discoverCommandFiles(home) {
|
|
219
420
|
const found = [];
|
|
@@ -249,6 +450,25 @@ function detectSecretSourceNames(config) {
|
|
|
249
450
|
names.push(key);
|
|
250
451
|
}
|
|
251
452
|
}
|
|
453
|
+
// Known nested credential locations in the OpenClaw 2026.6.x layout. Verified
|
|
454
|
+
// against a real install: the gateway auth token and per-provider apiKeys live
|
|
455
|
+
// in nested objects, not at the top level. We surface their dotted *names*
|
|
456
|
+
// only — the values are already carried (0600) in the gateway / custom_providers
|
|
457
|
+
// archive slices; nothing here ever exposes a value.
|
|
458
|
+
const gatewayToken = nestedValue(config, 'gateway.auth.token');
|
|
459
|
+
if (typeof gatewayToken === 'string' && gatewayToken.trim())
|
|
460
|
+
names.push('gateway.auth.token');
|
|
461
|
+
const providers = nestedValue(config, 'models.providers');
|
|
462
|
+
if (providers && typeof providers === 'object' && !Array.isArray(providers)) {
|
|
463
|
+
for (const [provName, prov] of Object.entries(providers)) {
|
|
464
|
+
if (prov && typeof prov === 'object' && !Array.isArray(prov)) {
|
|
465
|
+
const apiKey = prov.apiKey;
|
|
466
|
+
if (typeof apiKey === 'string' && apiKey.trim()) {
|
|
467
|
+
names.push(`models.providers.${provName}.apiKey`);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
252
472
|
return Array.from(new Set(names));
|
|
253
473
|
}
|
|
254
474
|
/** Resolve the action for a directly-importable category given the preset. */
|
|
@@ -277,6 +497,9 @@ export function buildClawMigrationPlan(opts = {}) {
|
|
|
277
497
|
target,
|
|
278
498
|
codebuddyDir,
|
|
279
499
|
};
|
|
500
|
+
// Identity/memory markdown files live either at the home root (legacy
|
|
501
|
+
// clawdbot/moltbot) or under the configured workspace dir (OpenClaw 2026.6.x).
|
|
502
|
+
const identityBases = identityBaseDirs(home, config?.raw ?? {});
|
|
280
503
|
// --- Directly imported identity files (consumed by bootstrap loader / prompt builder) ---
|
|
281
504
|
const identityFiles = [
|
|
282
505
|
{ category: 'persona', file: 'SOUL.md', dest: path.join(target, 'SOUL.md') },
|
|
@@ -284,15 +507,16 @@ export function buildClawMigrationPlan(opts = {}) {
|
|
|
284
507
|
{ category: 'agents', file: 'AGENTS.md', dest: path.join(target, 'AGENTS.md') },
|
|
285
508
|
];
|
|
286
509
|
for (const { category, file, dest } of identityFiles) {
|
|
287
|
-
const src =
|
|
288
|
-
if (
|
|
510
|
+
const src = findIdentityFile(identityBases, file);
|
|
511
|
+
if (src) {
|
|
512
|
+
const fromWorkspace = path.dirname(src) !== home;
|
|
289
513
|
entries.push({
|
|
290
514
|
category,
|
|
291
515
|
label: file,
|
|
292
516
|
action: importOrPresetArchive(category, ctx),
|
|
293
517
|
source: src,
|
|
294
518
|
destination: dest,
|
|
295
|
-
detail: `OpenClaw ${file} -> workspace ${file} (loaded as identity/bootstrap context).`,
|
|
519
|
+
detail: `OpenClaw ${file}${fromWorkspace ? ' (workspace)' : ''} -> workspace ${file} (loaded as identity/bootstrap context).`,
|
|
296
520
|
});
|
|
297
521
|
}
|
|
298
522
|
else {
|
|
@@ -301,9 +525,9 @@ export function buildClawMigrationPlan(opts = {}) {
|
|
|
301
525
|
}
|
|
302
526
|
// --- MEMORY.md -> project memory file ---
|
|
303
527
|
{
|
|
304
|
-
const src =
|
|
528
|
+
const src = findIdentityFile(identityBases, 'MEMORY.md');
|
|
305
529
|
const dest = path.join(codebuddyDir, 'CODEBUDDY_MEMORY.md');
|
|
306
|
-
if (
|
|
530
|
+
if (src) {
|
|
307
531
|
entries.push({
|
|
308
532
|
category: 'memory',
|
|
309
533
|
label: 'MEMORY.md',
|
|
@@ -417,6 +641,98 @@ export function buildClawMigrationPlan(opts = {}) {
|
|
|
417
641
|
entries.push({ category: 'agent_settings', label: 'agent behavior defaults', action: 'skip', source: null, destination: null, detail: 'No mappable agent behavior defaults in config.' });
|
|
418
642
|
}
|
|
419
643
|
}
|
|
644
|
+
// --- Promoted categories: cron, command_allowlist, memory_backend, exec_timeout, vision ---
|
|
645
|
+
// These were previously archived-for-review but now have confirmed Code Buddy
|
|
646
|
+
// consumers. Each gets a dedicated mapper that conservatively translates
|
|
647
|
+
// well-understood fields; unmapped / complex fields stay in the archive.
|
|
648
|
+
// cron -> CronScheduler jobs written to settings.json:cronJobs
|
|
649
|
+
{
|
|
650
|
+
const jobs = mapClawCronJobs(cfg);
|
|
651
|
+
if (jobs.length > 0) {
|
|
652
|
+
entries.push({
|
|
653
|
+
category: 'cron',
|
|
654
|
+
label: `cron jobs (${jobs.length})`,
|
|
655
|
+
action: importOrPresetArchive('cron', ctx),
|
|
656
|
+
source: 'config:cronJobs',
|
|
657
|
+
destination: settingsKeyDest(ctx, 'cronJobs'),
|
|
658
|
+
detail: `Mapped ${jobs.length} cron job(s): ${jobs.map((j) => j.name).join(', ')}.`,
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
else {
|
|
662
|
+
entries.push({ category: 'cron', label: 'cron jobs', action: 'skip', source: null, destination: null, detail: 'No mappable cron jobs in config.' });
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
// command_allowlist -> settings.json:commandAllowlist
|
|
666
|
+
{
|
|
667
|
+
const allowlist = mapClawCommandAllowlist(cfg);
|
|
668
|
+
if (allowlist.length > 0) {
|
|
669
|
+
entries.push({
|
|
670
|
+
category: 'command_allowlist',
|
|
671
|
+
label: `command allowlist (${allowlist.length})`,
|
|
672
|
+
action: importOrPresetArchive('command_allowlist', ctx),
|
|
673
|
+
source: 'config:commandAllowlist',
|
|
674
|
+
destination: settingsKeyDest(ctx, 'commandAllowlist'),
|
|
675
|
+
detail: `Imported: ${allowlist.join(', ')}.`,
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
else {
|
|
679
|
+
entries.push({ category: 'command_allowlist', label: 'command allowlist', action: 'skip', source: null, destination: null, detail: 'No mappable command allowlist in config.' });
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
// memory_backend -> settings.json:memoryProvider
|
|
683
|
+
{
|
|
684
|
+
const provider = mapClawMemoryBackend(cfg);
|
|
685
|
+
if (provider) {
|
|
686
|
+
entries.push({
|
|
687
|
+
category: 'memory_backend',
|
|
688
|
+
label: `memory backend (${provider})`,
|
|
689
|
+
action: importOrPresetArchive('memory_backend', ctx),
|
|
690
|
+
source: 'config:memoryBackend',
|
|
691
|
+
destination: settingsKeyDest(ctx, 'memoryProvider'),
|
|
692
|
+
detail: `Mapped memory provider: "${provider}".`,
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
else {
|
|
696
|
+
entries.push({ category: 'memory_backend', label: 'memory backend', action: 'skip', source: null, destination: null, detail: 'No mappable memory backend in config.' });
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
// exec_timeout -> settings.json:execTimeout
|
|
700
|
+
{
|
|
701
|
+
const timeout = mapClawExecTimeout(cfg);
|
|
702
|
+
if (timeout !== undefined) {
|
|
703
|
+
entries.push({
|
|
704
|
+
category: 'exec_timeout',
|
|
705
|
+
label: `exec timeout (${timeout}ms)`,
|
|
706
|
+
action: importOrPresetArchive('exec_timeout', ctx),
|
|
707
|
+
source: 'config:execTimeout',
|
|
708
|
+
destination: settingsKeyDest(ctx, 'execTimeout'),
|
|
709
|
+
detail: `Exec timeout: ${timeout}ms.`,
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
else {
|
|
713
|
+
entries.push({ category: 'exec_timeout', label: 'exec timeout', action: 'skip', source: null, destination: null, detail: 'No mappable exec timeout in config.' });
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
// vision -> settings.json:visionEnabled + visionModel
|
|
717
|
+
{
|
|
718
|
+
const vision = mapClawVisionSettings(cfg);
|
|
719
|
+
const mappedKeys = Object.keys(vision);
|
|
720
|
+
if (mappedKeys.length > 0) {
|
|
721
|
+
entries.push({
|
|
722
|
+
category: 'vision',
|
|
723
|
+
label: `vision config (${mappedKeys.join(', ')})`,
|
|
724
|
+
action: importOrPresetArchive('vision', ctx),
|
|
725
|
+
source: 'config:vision',
|
|
726
|
+
destination: settingsKeyDest(ctx, mappedKeys.join('+')),
|
|
727
|
+
detail: `Imported: ${mappedKeys
|
|
728
|
+
.map((k) => `${k}=${JSON.stringify(vision[k])}`)
|
|
729
|
+
.join(', ')}.`,
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
else {
|
|
733
|
+
entries.push({ category: 'vision', label: 'vision config', action: 'skip', source: null, destination: null, detail: 'No mappable vision settings in config.' });
|
|
734
|
+
}
|
|
735
|
+
}
|
|
420
736
|
// --- Archived-for-review categories (no confirmed live consumer / safety) ---
|
|
421
737
|
//
|
|
422
738
|
// Each category is archived (never imported) because Code Buddy has no
|
|
@@ -436,25 +752,20 @@ export function buildClawMigrationPlan(opts = {}) {
|
|
|
436
752
|
{ category: 'tts', keys: ['tts', 'textToSpeech', 'voice'], label: 'TTS / voice config' },
|
|
437
753
|
{ category: 'browser', keys: ['browser', 'browserSettings', 'browserBackend'], label: 'browser automation settings' },
|
|
438
754
|
{ category: 'tool_settings', keys: ['tools', 'toolSettings', 'tool_settings'], label: 'tool settings' },
|
|
439
|
-
{ category: 'command_allowlist', keys: ['commandAllowlist', 'allowlist', 'allowedCommands'], label: 'command allowlist' },
|
|
440
755
|
{ category: 'gateway', keys: ['gateway', 'gatewayConfig'], label: 'gateway config' },
|
|
441
|
-
{ category: 'cron', keys: ['cron', 'cronJobs', 'cronjobs'], label: 'cron jobs' },
|
|
442
756
|
{ category: 'plugins', keys: ['plugins'], label: 'plugins' },
|
|
443
757
|
// `hooks` and `webhooks` are split so each lands in its own archive slice;
|
|
444
758
|
// both can carry shell commands / credentials, so both archive (never run).
|
|
445
759
|
{ category: 'hooks', keys: ['hooks', 'lifecycleHooks'], label: 'lifecycle hooks', sensitive: true },
|
|
446
760
|
{ category: 'webhooks', keys: ['webhooks', 'webhookEndpoints'], label: 'webhook endpoints', sensitive: true },
|
|
447
|
-
{ category: 'memory_backend', keys: ['memoryBackend', 'memory_backend'], label: 'memory backend' },
|
|
448
761
|
{ category: 'agent_defaults', keys: ['agent', 'agentDefaults', 'multiAgent'], label: 'agent defaults / multi-agent' },
|
|
449
762
|
{ category: 'session_policies', keys: ['session', 'sessionReset', 'sessionPolicies'], label: 'session reset policies' },
|
|
450
763
|
{ category: 'approval_rules', keys: ['approval', 'approvalRules'], label: 'approval rules' },
|
|
451
|
-
{ category: 'exec_timeout', keys: ['execTimeout', 'exec_timeout', 'timeout'], label: 'exec timeout' },
|
|
452
764
|
// --- Expanded category set (toward upstream `hermes claw migrate` parity) ---
|
|
453
765
|
{ category: 'toolsets', keys: ['toolsets', 'toolSets', 'toolset'], label: 'toolsets' },
|
|
454
766
|
{ category: 'profiles', keys: ['profiles', 'agentProfiles'], label: 'agent profiles' },
|
|
455
767
|
{ category: 'bundles', keys: ['bundles', 'skillBundles'], label: 'bundles' },
|
|
456
768
|
{ category: 'pairing', keys: ['pairing', 'pairedDevices', 'devices'], label: 'device pairing / allowlist' },
|
|
457
|
-
{ category: 'vision', keys: ['vision', 'visionSettings'], label: 'vision config' },
|
|
458
769
|
{ category: 'image_video', keys: ['image', 'video', 'media'], label: 'image / video generation config' },
|
|
459
770
|
{ category: 'runtimes', keys: ['runtimes', 'backends', 'runtime'], label: 'runtime backends (Docker/SSH/Modal/Daytona)' },
|
|
460
771
|
{ category: 'portal', keys: ['portal', 'nousPortal', 'toolGateway'], label: 'Nous Portal Tool Gateway config', sensitive: true },
|
|
@@ -515,8 +826,25 @@ function backupSource(home, codebuddyDir) {
|
|
|
515
826
|
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
516
827
|
const backupRoot = path.join(codebuddyDir, 'openclaw-migration', 'backup', stamp);
|
|
517
828
|
ensureDir(backupRoot);
|
|
829
|
+
// The backup aggregates the WHOLE source tree — including ~/.openclaw/identity
|
|
830
|
+
// device credentials and openclaw.json (gateway token / provider apiKey). Lock
|
|
831
|
+
// the backup root to owner-only (0700) BEFORE copying so the secrets never sit
|
|
832
|
+
// under a world-traversable 0755 directory, even briefly.
|
|
833
|
+
try {
|
|
834
|
+
fs.chmodSync(backupRoot, 0o700);
|
|
835
|
+
}
|
|
836
|
+
catch {
|
|
837
|
+
/* best-effort on platforms without chmod */
|
|
838
|
+
}
|
|
518
839
|
// Shallow recursive copy of the source tree (fs.cpSync available on Node >= 16.7).
|
|
519
840
|
fs.cpSync(home, path.join(backupRoot, path.basename(home)), { recursive: true });
|
|
841
|
+
// cpSync preserves source perms; harden the copied home wrapper dir too.
|
|
842
|
+
try {
|
|
843
|
+
fs.chmodSync(path.join(backupRoot, path.basename(home)), 0o700);
|
|
844
|
+
}
|
|
845
|
+
catch {
|
|
846
|
+
/* best-effort */
|
|
847
|
+
}
|
|
520
848
|
return backupRoot;
|
|
521
849
|
}
|
|
522
850
|
function applyEntry(entry, opts, ctx) {
|
|
@@ -638,20 +966,93 @@ function applyEntry(entry, opts, ctx) {
|
|
|
638
966
|
entry.applied = true;
|
|
639
967
|
return;
|
|
640
968
|
}
|
|
969
|
+
case 'cron': {
|
|
970
|
+
const jobs = mapClawCronJobs(ctx.config?.raw ?? {});
|
|
971
|
+
if (jobs.length === 0)
|
|
972
|
+
return;
|
|
973
|
+
mergeSettings(ctx, (settings) => {
|
|
974
|
+
const existing = settings.cronJobs ?? [];
|
|
975
|
+
const mapped = jobs.map((j) => ({
|
|
976
|
+
name: j.name,
|
|
977
|
+
type: 'cron',
|
|
978
|
+
schedule: { cron: j.schedule },
|
|
979
|
+
task: { type: 'message', message: j.task },
|
|
980
|
+
enabled: true,
|
|
981
|
+
}));
|
|
982
|
+
settings.cronJobs = overwrite ? mapped : [...existing, ...mapped];
|
|
983
|
+
});
|
|
984
|
+
entry.applied = true;
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
case 'command_allowlist': {
|
|
988
|
+
const allowlist = mapClawCommandAllowlist(ctx.config?.raw ?? {});
|
|
989
|
+
if (allowlist.length === 0)
|
|
990
|
+
return;
|
|
991
|
+
mergeSettings(ctx, (settings) => {
|
|
992
|
+
const existing = settings.commandAllowlist ?? [];
|
|
993
|
+
settings.commandAllowlist = overwrite
|
|
994
|
+
? allowlist
|
|
995
|
+
: Array.from(new Set([...existing, ...allowlist]));
|
|
996
|
+
});
|
|
997
|
+
entry.applied = true;
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
case 'memory_backend': {
|
|
1001
|
+
const provider = mapClawMemoryBackend(ctx.config?.raw ?? {});
|
|
1002
|
+
if (!provider)
|
|
1003
|
+
return;
|
|
1004
|
+
mergeSettings(ctx, (settings) => {
|
|
1005
|
+
if (overwrite || settings.memoryProvider === undefined) {
|
|
1006
|
+
settings.memoryProvider = provider;
|
|
1007
|
+
}
|
|
1008
|
+
});
|
|
1009
|
+
entry.applied = true;
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
case 'exec_timeout': {
|
|
1013
|
+
const timeout = mapClawExecTimeout(ctx.config?.raw ?? {});
|
|
1014
|
+
if (timeout === undefined)
|
|
1015
|
+
return;
|
|
1016
|
+
mergeSettings(ctx, (settings) => {
|
|
1017
|
+
if (overwrite || settings.execTimeout === undefined) {
|
|
1018
|
+
settings.execTimeout = timeout;
|
|
1019
|
+
}
|
|
1020
|
+
});
|
|
1021
|
+
entry.applied = true;
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
case 'vision': {
|
|
1025
|
+
const vision = mapClawVisionSettings(ctx.config?.raw ?? {});
|
|
1026
|
+
if (Object.keys(vision).length === 0)
|
|
1027
|
+
return;
|
|
1028
|
+
mergeSettings(ctx, (settings) => {
|
|
1029
|
+
const set = (key, value) => {
|
|
1030
|
+
if (value !== undefined && (overwrite || settings[key] === undefined))
|
|
1031
|
+
settings[key] = value;
|
|
1032
|
+
};
|
|
1033
|
+
set('visionEnabled', vision.visionEnabled);
|
|
1034
|
+
set('visionModel', vision.visionModel);
|
|
1035
|
+
});
|
|
1036
|
+
entry.applied = true;
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
641
1039
|
default: {
|
|
642
1040
|
// Archive categories -> write the raw config slice to the review dir.
|
|
643
1041
|
if (entry.action !== 'archive' || !entry.destination || !entry.source)
|
|
644
1042
|
return;
|
|
645
1043
|
ensureDir(path.dirname(entry.destination));
|
|
646
1044
|
const slice = sliceForArchive(entry, ctx);
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
1045
|
+
// Archive slices are credential-review artifacts. Write owner-only (0600)
|
|
1046
|
+
// for EVERY category (not just a hand-maintained sensitive set) so a
|
|
1047
|
+
// mis-tagged or newly-added credential-bearing category can never be left
|
|
1048
|
+
// world-readable. Create at 0600 to close the write-then-chmod window,
|
|
1049
|
+
// then re-harden defensively.
|
|
1050
|
+
fs.writeFileSync(entry.destination, JSON.stringify(slice, null, 2), { encoding: 'utf-8', mode: 0o600 });
|
|
1051
|
+
try {
|
|
1052
|
+
fs.chmodSync(entry.destination, 0o600);
|
|
1053
|
+
}
|
|
1054
|
+
catch {
|
|
1055
|
+
/* best-effort on platforms without chmod */
|
|
655
1056
|
}
|
|
656
1057
|
entry.applied = true;
|
|
657
1058
|
}
|