@gguf/claw
Version:
WhatsApp gateway CLI (Baileys web) with Pi RPC agent
1,301 lines (1,289 loc) • 177 kB
JavaScript
import { t as __exportAll } from "./rolldown-runtime-Cbj13DAv.js";
import { B as resolveConfigPath, F as CONFIG_PATH, G as resolveIsNixMode, H as resolveDefaultConfigCandidates, I as DEFAULT_GATEWAY_PORT, J as resolveOAuthDir, K as resolveLegacyStateDirs, L as STATE_DIR, R as isNixMode, U as resolveGatewayLockDir, V as resolveConfigPathCandidate, W as resolveGatewayPort, X as resolveStateDir, Y as resolveOAuthPath, Z as CHANNEL_IDS, ot as normalizeChatChannelId, q as resolveNewStateDir, z as resolveCanonicalConfigPath } from "./entry.js";
import { A as parseModelRef, ct as loadShellEnvFallback, dt as shouldEnableShellEnvFallback, ht as DEFAULT_CONTEXT_TOKENS, lt as resolveShellEnvFallbackTimeoutMs, ut as shouldDeferShellEnvFallback } from "./auth-profiles-CYBuGiBb.js";
import { c as normalizeAgentId, n as DEFAULT_AGENT_ID } from "./session-key-CZkcvAtx.js";
import { m as resolveUserPath } from "./utils-DX85MiPR.js";
import { c as resolveDefaultAgentId, s as resolveAgentWorkspaceDir } from "./agent-scope-C9VjJXEK.js";
import { c as resolveEnableState, l as resolveMemorySlotDecision, s as normalizePluginsConfig, t as loadPluginManifestRegistry } from "./manifest-registry-C69Z-I4v.js";
import { createRequire } from "node:module";
import path from "node:path";
import os from "node:os";
import fs from "node:fs";
import JSON5 from "json5";
import crypto from "node:crypto";
import AjvPkg from "ajv";
import { z } from "zod";
//#region src/version.ts
const CORE_PACKAGE_NAME = "openclaw";
const PACKAGE_JSON_CANDIDATES = [
"../package.json",
"../../package.json",
"../../../package.json",
"./package.json"
];
const BUILD_INFO_CANDIDATES = [
"../build-info.json",
"../../build-info.json",
"./build-info.json"
];
function readVersionFromJsonCandidates(moduleUrl, candidates, opts = {}) {
try {
const require = createRequire(moduleUrl);
for (const candidate of candidates) try {
const parsed = require(candidate);
const version = parsed.version?.trim();
if (!version) continue;
if (opts.requirePackageName && parsed.name !== CORE_PACKAGE_NAME) continue;
return version;
} catch {}
return null;
} catch {
return null;
}
}
function readVersionFromPackageJsonForModuleUrl(moduleUrl) {
return readVersionFromJsonCandidates(moduleUrl, PACKAGE_JSON_CANDIDATES, { requirePackageName: true });
}
function readVersionFromBuildInfoForModuleUrl(moduleUrl) {
return readVersionFromJsonCandidates(moduleUrl, BUILD_INFO_CANDIDATES);
}
function resolveVersionFromModuleUrl(moduleUrl) {
return readVersionFromPackageJsonForModuleUrl(moduleUrl) || readVersionFromBuildInfoForModuleUrl(moduleUrl);
}
const VERSION = typeof __OPENCLAW_VERSION__ === "string" && __OPENCLAW_VERSION__ || process.env.OPENCLAW_BUNDLED_VERSION || resolveVersionFromModuleUrl(import.meta.url) || "0.0.0";
//#endregion
//#region src/config/agent-dirs.ts
var DuplicateAgentDirError = class extends Error {
constructor(duplicates) {
super(formatDuplicateAgentDirError(duplicates));
this.name = "DuplicateAgentDirError";
this.duplicates = duplicates;
}
};
function canonicalizeAgentDir(agentDir) {
const resolved = path.resolve(agentDir);
if (process.platform === "darwin" || process.platform === "win32") return resolved.toLowerCase();
return resolved;
}
function collectReferencedAgentIds(cfg) {
const ids = /* @__PURE__ */ new Set();
const agents = Array.isArray(cfg.agents?.list) ? cfg.agents?.list : [];
const defaultAgentId = agents.find((agent) => agent?.default)?.id ?? agents[0]?.id ?? DEFAULT_AGENT_ID;
ids.add(normalizeAgentId(defaultAgentId));
for (const entry of agents) if (entry?.id) ids.add(normalizeAgentId(entry.id));
const bindings = cfg.bindings;
if (Array.isArray(bindings)) for (const binding of bindings) {
const id = binding?.agentId;
if (typeof id === "string" && id.trim()) ids.add(normalizeAgentId(id));
}
return [...ids];
}
function resolveEffectiveAgentDir(cfg, agentId, deps) {
const id = normalizeAgentId(agentId);
const trimmed = (Array.isArray(cfg.agents?.list) ? cfg.agents?.list.find((agent) => normalizeAgentId(agent.id) === id)?.agentDir : void 0)?.trim();
if (trimmed) return resolveUserPath(trimmed);
const root = resolveStateDir(deps?.env ?? process.env, deps?.homedir ?? os.homedir);
return path.join(root, "agents", id, "agent");
}
function findDuplicateAgentDirs(cfg, deps) {
const byDir = /* @__PURE__ */ new Map();
for (const agentId of collectReferencedAgentIds(cfg)) {
const agentDir = resolveEffectiveAgentDir(cfg, agentId, deps);
const key = canonicalizeAgentDir(agentDir);
const entry = byDir.get(key);
if (entry) entry.agentIds.push(agentId);
else byDir.set(key, {
agentDir,
agentIds: [agentId]
});
}
return [...byDir.values()].filter((v) => v.agentIds.length > 1);
}
function formatDuplicateAgentDirError(dups) {
return [
"Duplicate agentDir detected (multi-agent config).",
"Each agent must have a unique agentDir; sharing it causes auth/session state collisions and token invalidation.",
"",
"Conflicts:",
...dups.map((d) => `- ${d.agentDir}: ${d.agentIds.map((id) => `"${id}"`).join(", ")}`),
"",
"Fix: remove the shared agents.list[].agentDir override (or give each agent its own directory).",
"If you want to share credentials, copy auth-profiles.json instead of sharing the entire agentDir."
].join("\n");
}
//#endregion
//#region src/config/agent-limits.ts
const DEFAULT_AGENT_MAX_CONCURRENT = 4;
const DEFAULT_SUBAGENT_MAX_CONCURRENT = 8;
function resolveAgentMaxConcurrent(cfg) {
const raw = cfg?.agents?.defaults?.maxConcurrent;
if (typeof raw === "number" && Number.isFinite(raw)) return Math.max(1, Math.floor(raw));
return DEFAULT_AGENT_MAX_CONCURRENT;
}
function resolveSubagentMaxConcurrent(cfg) {
const raw = cfg?.agents?.defaults?.subagents?.maxConcurrent;
if (typeof raw === "number" && Number.isFinite(raw)) return Math.max(1, Math.floor(raw));
return DEFAULT_SUBAGENT_MAX_CONCURRENT;
}
//#endregion
//#region src/config/talk.ts
function readTalkApiKeyFromProfile(deps = {}) {
const fsImpl = deps.fs ?? fs;
const osImpl = deps.os ?? os;
const pathImpl = deps.path ?? path;
const home = osImpl.homedir();
const candidates = [
".profile",
".zprofile",
".zshrc",
".bashrc"
].map((name) => pathImpl.join(home, name));
for (const candidate of candidates) {
if (!fsImpl.existsSync(candidate)) continue;
try {
const value = fsImpl.readFileSync(candidate, "utf-8").match(/(?:^|\n)\s*(?:export\s+)?ELEVENLABS_API_KEY\s*=\s*["']?([^\n"']+)["']?/)?.[1]?.trim();
if (value) return value;
} catch {}
}
return null;
}
function resolveTalkApiKey(env = process.env, deps = {}) {
const envValue = (env.ELEVENLABS_API_KEY ?? "").trim();
if (envValue) return envValue;
return readTalkApiKeyFromProfile(deps);
}
//#endregion
//#region src/config/defaults.ts
let defaultWarnState = { warned: false };
const DEFAULT_MODEL_ALIASES = {
opus: "anthropic/claude-opus-4-6",
sonnet: "anthropic/claude-sonnet-4-5",
gpt: "openai/gpt-5.2",
"gpt-mini": "openai/gpt-5-mini",
gemini: "google/gemini-3-pro-preview",
"gemini-flash": "google/gemini-3-flash-preview"
};
const DEFAULT_MODEL_COST = {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0
};
const DEFAULT_MODEL_INPUT = ["text"];
const DEFAULT_MODEL_MAX_TOKENS = 8192;
function isPositiveNumber(value) {
return typeof value === "number" && Number.isFinite(value) && value > 0;
}
function resolveModelCost(raw) {
return {
input: typeof raw?.input === "number" ? raw.input : DEFAULT_MODEL_COST.input,
output: typeof raw?.output === "number" ? raw.output : DEFAULT_MODEL_COST.output,
cacheRead: typeof raw?.cacheRead === "number" ? raw.cacheRead : DEFAULT_MODEL_COST.cacheRead,
cacheWrite: typeof raw?.cacheWrite === "number" ? raw.cacheWrite : DEFAULT_MODEL_COST.cacheWrite
};
}
function resolveAnthropicDefaultAuthMode(cfg) {
const profiles = cfg.auth?.profiles ?? {};
const anthropicProfiles = Object.entries(profiles).filter(([, profile]) => profile?.provider === "anthropic");
const order = cfg.auth?.order?.anthropic ?? [];
for (const profileId of order) {
const entry = profiles[profileId];
if (!entry || entry.provider !== "anthropic") continue;
if (entry.mode === "api_key") return "api_key";
if (entry.mode === "oauth" || entry.mode === "token") return "oauth";
}
const hasApiKey = anthropicProfiles.some(([, profile]) => profile?.mode === "api_key");
const hasOauth = anthropicProfiles.some(([, profile]) => profile?.mode === "oauth" || profile?.mode === "token");
if (hasApiKey && !hasOauth) return "api_key";
if (hasOauth && !hasApiKey) return "oauth";
if (process.env.ANTHROPIC_OAUTH_TOKEN?.trim()) return "oauth";
if (process.env.ANTHROPIC_API_KEY?.trim()) return "api_key";
return null;
}
function resolvePrimaryModelRef(raw) {
if (!raw || typeof raw !== "string") return null;
const trimmed = raw.trim();
if (!trimmed) return null;
return DEFAULT_MODEL_ALIASES[trimmed.toLowerCase()] ?? trimmed;
}
function applyMessageDefaults(cfg) {
const messages = cfg.messages;
if (messages?.ackReactionScope !== void 0) return cfg;
const nextMessages = messages ? { ...messages } : {};
nextMessages.ackReactionScope = "group-mentions";
return {
...cfg,
messages: nextMessages
};
}
function applySessionDefaults(cfg, options = {}) {
const session = cfg.session;
if (!session || session.mainKey === void 0) return cfg;
const trimmed = session.mainKey.trim();
const warn = options.warn ?? console.warn;
const warnState = options.warnState ?? defaultWarnState;
const next = {
...cfg,
session: {
...session,
mainKey: "main"
}
};
if (trimmed && trimmed !== "main" && !warnState.warned) {
warnState.warned = true;
warn("session.mainKey is ignored; main session is always \"main\".");
}
return next;
}
function applyTalkApiKey(config) {
const resolved = resolveTalkApiKey();
if (!resolved) return config;
if (config.talk?.apiKey?.trim()) return config;
return {
...config,
talk: {
...config.talk,
apiKey: resolved
}
};
}
function applyModelDefaults(cfg) {
let mutated = false;
let nextCfg = cfg;
const providerConfig = nextCfg.models?.providers;
if (providerConfig) {
const nextProviders = { ...providerConfig };
for (const [providerId, provider] of Object.entries(providerConfig)) {
const models = provider.models;
if (!Array.isArray(models) || models.length === 0) continue;
let providerMutated = false;
const nextModels = models.map((model) => {
const raw = model;
let modelMutated = false;
const reasoning = typeof raw.reasoning === "boolean" ? raw.reasoning : false;
if (raw.reasoning !== reasoning) modelMutated = true;
const input = raw.input ?? [...DEFAULT_MODEL_INPUT];
if (raw.input === void 0) modelMutated = true;
const cost = resolveModelCost(raw.cost);
if (!raw.cost || raw.cost.input !== cost.input || raw.cost.output !== cost.output || raw.cost.cacheRead !== cost.cacheRead || raw.cost.cacheWrite !== cost.cacheWrite) modelMutated = true;
const contextWindow = isPositiveNumber(raw.contextWindow) ? raw.contextWindow : DEFAULT_CONTEXT_TOKENS;
if (raw.contextWindow !== contextWindow) modelMutated = true;
const defaultMaxTokens = Math.min(DEFAULT_MODEL_MAX_TOKENS, contextWindow);
const maxTokens = isPositiveNumber(raw.maxTokens) ? raw.maxTokens : defaultMaxTokens;
if (raw.maxTokens !== maxTokens) modelMutated = true;
if (!modelMutated) return model;
providerMutated = true;
return {
...raw,
reasoning,
input,
cost,
contextWindow,
maxTokens
};
});
if (!providerMutated) continue;
nextProviders[providerId] = {
...provider,
models: nextModels
};
mutated = true;
}
if (mutated) nextCfg = {
...nextCfg,
models: {
...nextCfg.models,
providers: nextProviders
}
};
}
const existingAgent = nextCfg.agents?.defaults;
if (!existingAgent) return mutated ? nextCfg : cfg;
const existingModels = existingAgent.models ?? {};
if (Object.keys(existingModels).length === 0) return mutated ? nextCfg : cfg;
const nextModels = { ...existingModels };
for (const [alias, target] of Object.entries(DEFAULT_MODEL_ALIASES)) {
const entry = nextModels[target];
if (!entry) continue;
if (entry.alias !== void 0) continue;
nextModels[target] = {
...entry,
alias
};
mutated = true;
}
if (!mutated) return cfg;
return {
...nextCfg,
agents: {
...nextCfg.agents,
defaults: {
...existingAgent,
models: nextModels
}
}
};
}
function applyAgentDefaults(cfg) {
const agents = cfg.agents;
const defaults = agents?.defaults;
const hasMax = typeof defaults?.maxConcurrent === "number" && Number.isFinite(defaults.maxConcurrent);
const hasSubMax = typeof defaults?.subagents?.maxConcurrent === "number" && Number.isFinite(defaults.subagents.maxConcurrent);
if (hasMax && hasSubMax) return cfg;
let mutated = false;
const nextDefaults = defaults ? { ...defaults } : {};
if (!hasMax) {
nextDefaults.maxConcurrent = DEFAULT_AGENT_MAX_CONCURRENT;
mutated = true;
}
const nextSubagents = defaults?.subagents ? { ...defaults.subagents } : {};
if (!hasSubMax) {
nextSubagents.maxConcurrent = DEFAULT_SUBAGENT_MAX_CONCURRENT;
mutated = true;
}
if (!mutated) return cfg;
return {
...cfg,
agents: {
...agents,
defaults: {
...nextDefaults,
subagents: nextSubagents
}
}
};
}
function applyLoggingDefaults(cfg) {
const logging = cfg.logging;
if (!logging) return cfg;
if (logging.redactSensitive) return cfg;
return {
...cfg,
logging: {
...logging,
redactSensitive: "tools"
}
};
}
function applyContextPruningDefaults(cfg) {
const defaults = cfg.agents?.defaults;
if (!defaults) return cfg;
const authMode = resolveAnthropicDefaultAuthMode(cfg);
if (!authMode) return cfg;
let mutated = false;
const nextDefaults = { ...defaults };
const contextPruning = defaults.contextPruning ?? {};
const heartbeat = defaults.heartbeat ?? {};
if (defaults.contextPruning?.mode === void 0) {
nextDefaults.contextPruning = {
...contextPruning,
mode: "cache-ttl",
ttl: defaults.contextPruning?.ttl ?? "1h"
};
mutated = true;
}
if (defaults.heartbeat?.every === void 0) {
nextDefaults.heartbeat = {
...heartbeat,
every: authMode === "oauth" ? "1h" : "30m"
};
mutated = true;
}
if (authMode === "api_key") {
const nextModels = defaults.models ? { ...defaults.models } : {};
let modelsMutated = false;
for (const [key, entry] of Object.entries(nextModels)) {
const parsed = parseModelRef(key, "anthropic");
if (!parsed || parsed.provider !== "anthropic") continue;
const current = entry ?? {};
const params = current.params ?? {};
if (typeof params.cacheRetention === "string") continue;
nextModels[key] = {
...current,
params: {
...params,
cacheRetention: "short"
}
};
modelsMutated = true;
}
const primary = resolvePrimaryModelRef(defaults.model?.primary ?? void 0);
if (primary) {
const parsedPrimary = parseModelRef(primary, "anthropic");
if (parsedPrimary?.provider === "anthropic") {
const key = `${parsedPrimary.provider}/${parsedPrimary.model}`;
const current = nextModels[key] ?? {};
const params = current.params ?? {};
if (typeof params.cacheRetention !== "string") {
nextModels[key] = {
...current,
params: {
...params,
cacheRetention: "short"
}
};
modelsMutated = true;
}
}
}
if (modelsMutated) {
nextDefaults.models = nextModels;
mutated = true;
}
}
if (!mutated) return cfg;
return {
...cfg,
agents: {
...cfg.agents,
defaults: nextDefaults
}
};
}
function applyCompactionDefaults(cfg) {
const defaults = cfg.agents?.defaults;
if (!defaults) return cfg;
const compaction = defaults?.compaction;
if (compaction?.mode) return cfg;
return {
...cfg,
agents: {
...cfg.agents,
defaults: {
...defaults,
compaction: {
...compaction,
mode: "safeguard"
}
}
}
};
}
//#endregion
//#region src/config/env-substitution.ts
/**
* Environment variable substitution for config values.
*
* Supports `${VAR_NAME}` syntax in string values, substituted at config load time.
* - Only uppercase env vars are matched: `[A-Z_][A-Z0-9_]*`
* - Escape with `$${}` to output literal `${}`
* - Missing env vars throw `MissingEnvVarError` with context
*
* @example
* ```json5
* {
* models: {
* providers: {
* "vercel-gateway": {
* apiKey: "${VERCEL_GATEWAY_API_KEY}"
* }
* }
* }
* }
* ```
*/
const ENV_VAR_NAME_PATTERN = /^[A-Z_][A-Z0-9_]*$/;
var MissingEnvVarError = class extends Error {
constructor(varName, configPath) {
super(`Missing env var "${varName}" referenced at config path: ${configPath}`);
this.varName = varName;
this.configPath = configPath;
this.name = "MissingEnvVarError";
}
};
function isPlainObject$4(value) {
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.prototype.toString.call(value) === "[object Object]";
}
function substituteString(value, env, configPath) {
if (!value.includes("$")) return value;
const chunks = [];
for (let i = 0; i < value.length; i += 1) {
const char = value[i];
if (char !== "$") {
chunks.push(char);
continue;
}
const next = value[i + 1];
const afterNext = value[i + 2];
if (next === "$" && afterNext === "{") {
const start = i + 3;
const end = value.indexOf("}", start);
if (end !== -1) {
const name = value.slice(start, end);
if (ENV_VAR_NAME_PATTERN.test(name)) {
chunks.push(`\${${name}}`);
i = end;
continue;
}
}
}
if (next === "{") {
const start = i + 2;
const end = value.indexOf("}", start);
if (end !== -1) {
const name = value.slice(start, end);
if (ENV_VAR_NAME_PATTERN.test(name)) {
const envValue = env[name];
if (envValue === void 0 || envValue === "") throw new MissingEnvVarError(name, configPath);
chunks.push(envValue);
i = end;
continue;
}
}
}
chunks.push(char);
}
return chunks.join("");
}
function substituteAny(value, env, path) {
if (typeof value === "string") return substituteString(value, env, path);
if (Array.isArray(value)) return value.map((item, index) => substituteAny(item, env, `${path}[${index}]`));
if (isPlainObject$4(value)) {
const result = {};
for (const [key, val] of Object.entries(value)) result[key] = substituteAny(val, env, path ? `${path}.${key}` : key);
return result;
}
return value;
}
/**
* Resolves `${VAR_NAME}` environment variable references in config values.
*
* @param obj - The parsed config object (after JSON5 parse and $include resolution)
* @param env - Environment variables to use for substitution (defaults to process.env)
* @returns The config object with env vars substituted
* @throws {MissingEnvVarError} If a referenced env var is not set or empty
*/
function resolveConfigEnvVars(obj, env = process.env) {
return substituteAny(obj, env, "");
}
//#endregion
//#region src/config/env-vars.ts
function collectConfigEnvVars(cfg) {
const envConfig = cfg?.env;
if (!envConfig) return {};
const entries = {};
if (envConfig.vars) for (const [key, value] of Object.entries(envConfig.vars)) {
if (!value) continue;
entries[key] = value;
}
for (const [key, value] of Object.entries(envConfig)) {
if (key === "shellEnv" || key === "vars") continue;
if (typeof value !== "string" || !value.trim()) continue;
entries[key] = value;
}
return entries;
}
//#endregion
//#region src/config/includes.ts
/**
* Config includes: $include directive for modular configs
*
* @example
* ```json5
* {
* "$include": "./base.json5", // single file
* "$include": ["./a.json5", "./b.json5"] // merge multiple
* }
* ```
*/
const INCLUDE_KEY = "$include";
const MAX_INCLUDE_DEPTH = 10;
var ConfigIncludeError = class extends Error {
constructor(message, includePath, cause) {
super(message);
this.includePath = includePath;
this.cause = cause;
this.name = "ConfigIncludeError";
}
};
var CircularIncludeError = class extends ConfigIncludeError {
constructor(chain) {
super(`Circular include detected: ${chain.join(" -> ")}`, chain[chain.length - 1]);
this.chain = chain;
this.name = "CircularIncludeError";
}
};
function isPlainObject$3(value) {
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.prototype.toString.call(value) === "[object Object]";
}
/** Deep merge: arrays concatenate, objects merge recursively, primitives: source wins */
function deepMerge(target, source) {
if (Array.isArray(target) && Array.isArray(source)) return [...target, ...source];
if (isPlainObject$3(target) && isPlainObject$3(source)) {
const result = { ...target };
for (const key of Object.keys(source)) result[key] = key in result ? deepMerge(result[key], source[key]) : source[key];
return result;
}
return source;
}
var IncludeProcessor = class IncludeProcessor {
constructor(basePath, resolver) {
this.basePath = basePath;
this.resolver = resolver;
this.visited = /* @__PURE__ */ new Set();
this.depth = 0;
this.visited.add(path.normalize(basePath));
}
process(obj) {
if (Array.isArray(obj)) return obj.map((item) => this.process(item));
if (!isPlainObject$3(obj)) return obj;
if (!(INCLUDE_KEY in obj)) return this.processObject(obj);
return this.processInclude(obj);
}
processObject(obj) {
const result = {};
for (const [key, value] of Object.entries(obj)) result[key] = this.process(value);
return result;
}
processInclude(obj) {
const includeValue = obj[INCLUDE_KEY];
const otherKeys = Object.keys(obj).filter((k) => k !== INCLUDE_KEY);
const included = this.resolveInclude(includeValue);
if (otherKeys.length === 0) return included;
if (!isPlainObject$3(included)) throw new ConfigIncludeError("Sibling keys require included content to be an object", typeof includeValue === "string" ? includeValue : INCLUDE_KEY);
const rest = {};
for (const key of otherKeys) rest[key] = this.process(obj[key]);
return deepMerge(included, rest);
}
resolveInclude(value) {
if (typeof value === "string") return this.loadFile(value);
if (Array.isArray(value)) return value.reduce((merged, item) => {
if (typeof item !== "string") throw new ConfigIncludeError(`Invalid $include array item: expected string, got ${typeof item}`, String(item));
return deepMerge(merged, this.loadFile(item));
}, {});
throw new ConfigIncludeError(`Invalid $include value: expected string or array of strings, got ${typeof value}`, String(value));
}
loadFile(includePath) {
const resolvedPath = this.resolvePath(includePath);
this.checkCircular(resolvedPath);
this.checkDepth(includePath);
const raw = this.readFile(includePath, resolvedPath);
const parsed = this.parseFile(includePath, resolvedPath, raw);
return this.processNested(resolvedPath, parsed);
}
resolvePath(includePath) {
const resolved = path.isAbsolute(includePath) ? includePath : path.resolve(path.dirname(this.basePath), includePath);
return path.normalize(resolved);
}
checkCircular(resolvedPath) {
if (this.visited.has(resolvedPath)) throw new CircularIncludeError([...this.visited, resolvedPath]);
}
checkDepth(includePath) {
if (this.depth >= MAX_INCLUDE_DEPTH) throw new ConfigIncludeError(`Maximum include depth (${MAX_INCLUDE_DEPTH}) exceeded at: ${includePath}`, includePath);
}
readFile(includePath, resolvedPath) {
try {
return this.resolver.readFile(resolvedPath);
} catch (err) {
throw new ConfigIncludeError(`Failed to read include file: ${includePath} (resolved: ${resolvedPath})`, includePath, err instanceof Error ? err : void 0);
}
}
parseFile(includePath, resolvedPath, raw) {
try {
return this.resolver.parseJson(raw);
} catch (err) {
throw new ConfigIncludeError(`Failed to parse include file: ${includePath} (resolved: ${resolvedPath})`, includePath, err instanceof Error ? err : void 0);
}
}
processNested(resolvedPath, parsed) {
const nested = new IncludeProcessor(resolvedPath, this.resolver);
nested.visited = new Set([...this.visited, resolvedPath]);
nested.depth = this.depth + 1;
return nested.process(parsed);
}
};
const defaultResolver = {
readFile: (p) => fs.readFileSync(p, "utf-8"),
parseJson: (raw) => JSON5.parse(raw)
};
/**
* Resolves all $include directives in a parsed config object.
*/
function resolveConfigIncludes(obj, configPath, resolver = defaultResolver) {
return new IncludeProcessor(configPath, resolver).process(obj);
}
//#endregion
//#region src/config/legacy.shared.ts
const isRecord$1 = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
const getRecord = (value) => isRecord$1(value) ? value : null;
const ensureRecord = (root, key) => {
const existing = root[key];
if (isRecord$1(existing)) return existing;
const next = {};
root[key] = next;
return next;
};
const mergeMissing = (target, source) => {
for (const [key, value] of Object.entries(source)) {
if (value === void 0) continue;
const existing = target[key];
if (existing === void 0) {
target[key] = value;
continue;
}
if (isRecord$1(existing) && isRecord$1(value)) mergeMissing(existing, value);
}
};
const AUDIO_TRANSCRIPTION_CLI_ALLOWLIST = new Set(["whisper"]);
const mapLegacyAudioTranscription = (value) => {
const transcriber = getRecord(value);
const command = Array.isArray(transcriber?.command) ? transcriber?.command : null;
if (!command || command.length === 0) return null;
const rawExecutable = String(command[0] ?? "").trim();
if (!rawExecutable) return null;
const executableName = rawExecutable.split(/[\\/]/).pop() ?? rawExecutable;
if (!AUDIO_TRANSCRIPTION_CLI_ALLOWLIST.has(executableName)) return null;
const args = command.slice(1).map((part) => String(part));
const timeoutSeconds = typeof transcriber?.timeoutSeconds === "number" ? transcriber?.timeoutSeconds : void 0;
const result = {
command: rawExecutable,
type: "cli"
};
if (args.length > 0) result.args = args;
if (timeoutSeconds !== void 0) result.timeoutSeconds = timeoutSeconds;
return result;
};
const getAgentsList = (agents) => {
const list = agents?.list;
return Array.isArray(list) ? list : [];
};
const resolveDefaultAgentIdFromRaw = (raw) => {
const list = getAgentsList(getRecord(raw.agents));
const defaultEntry = list.find((entry) => isRecord$1(entry) && entry.default === true && typeof entry.id === "string" && entry.id.trim() !== "");
if (defaultEntry) return defaultEntry.id.trim();
const routing = getRecord(raw.routing);
const routingDefault = typeof routing?.defaultAgentId === "string" ? routing.defaultAgentId.trim() : "";
if (routingDefault) return routingDefault;
const firstEntry = list.find((entry) => isRecord$1(entry) && typeof entry.id === "string" && entry.id.trim() !== "");
if (firstEntry) return firstEntry.id.trim();
return "main";
};
const ensureAgentEntry = (list, id) => {
const normalized = id.trim();
const existing = list.find((entry) => isRecord$1(entry) && typeof entry.id === "string" && entry.id.trim() === normalized);
if (existing) return existing;
const created = { id: normalized };
list.push(created);
return created;
};
//#endregion
//#region src/config/legacy.migrations.part-1.ts
const LEGACY_CONFIG_MIGRATIONS_PART_1 = [
{
id: "bindings.match.provider->bindings.match.channel",
describe: "Move bindings[].match.provider to bindings[].match.channel",
apply: (raw, changes) => {
const bindings = Array.isArray(raw.bindings) ? raw.bindings : null;
if (!bindings) return;
let touched = false;
for (const entry of bindings) {
if (!isRecord$1(entry)) continue;
const match = getRecord(entry.match);
if (!match) continue;
if (typeof match.channel === "string" && match.channel.trim()) continue;
const provider = typeof match.provider === "string" ? match.provider.trim() : "";
if (!provider) continue;
match.channel = provider;
delete match.provider;
entry.match = match;
touched = true;
}
if (touched) {
raw.bindings = bindings;
changes.push("Moved bindings[].match.provider → bindings[].match.channel.");
}
}
},
{
id: "bindings.match.accountID->bindings.match.accountId",
describe: "Move bindings[].match.accountID to bindings[].match.accountId",
apply: (raw, changes) => {
const bindings = Array.isArray(raw.bindings) ? raw.bindings : null;
if (!bindings) return;
let touched = false;
for (const entry of bindings) {
if (!isRecord$1(entry)) continue;
const match = getRecord(entry.match);
if (!match) continue;
if (match.accountId !== void 0) continue;
const accountID = typeof match.accountID === "string" ? match.accountID.trim() : match.accountID;
if (!accountID) continue;
match.accountId = accountID;
delete match.accountID;
entry.match = match;
touched = true;
}
if (touched) {
raw.bindings = bindings;
changes.push("Moved bindings[].match.accountID → bindings[].match.accountId.");
}
}
},
{
id: "session.sendPolicy.rules.match.provider->match.channel",
describe: "Move session.sendPolicy.rules[].match.provider to match.channel",
apply: (raw, changes) => {
const session = getRecord(raw.session);
if (!session) return;
const sendPolicy = getRecord(session.sendPolicy);
if (!sendPolicy) return;
const rules = Array.isArray(sendPolicy.rules) ? sendPolicy.rules : null;
if (!rules) return;
let touched = false;
for (const rule of rules) {
if (!isRecord$1(rule)) continue;
const match = getRecord(rule.match);
if (!match) continue;
if (typeof match.channel === "string" && match.channel.trim()) continue;
const provider = typeof match.provider === "string" ? match.provider.trim() : "";
if (!provider) continue;
match.channel = provider;
delete match.provider;
rule.match = match;
touched = true;
}
if (touched) {
sendPolicy.rules = rules;
session.sendPolicy = sendPolicy;
raw.session = session;
changes.push("Moved session.sendPolicy.rules[].match.provider → match.channel.");
}
}
},
{
id: "messages.queue.byProvider->byChannel",
describe: "Move messages.queue.byProvider to messages.queue.byChannel",
apply: (raw, changes) => {
const messages = getRecord(raw.messages);
if (!messages) return;
const queue = getRecord(messages.queue);
if (!queue) return;
if (queue.byProvider === void 0) return;
if (queue.byChannel === void 0) {
queue.byChannel = queue.byProvider;
changes.push("Moved messages.queue.byProvider → messages.queue.byChannel.");
} else changes.push("Removed messages.queue.byProvider (messages.queue.byChannel already set).");
delete queue.byProvider;
messages.queue = queue;
raw.messages = messages;
}
},
{
id: "providers->channels",
describe: "Move provider config sections to channels.*",
apply: (raw, changes) => {
const legacyEntries = [
"whatsapp",
"telegram",
"discord",
"slack",
"signal",
"imessage",
"msteams"
].filter((key) => isRecord$1(raw[key]));
if (legacyEntries.length === 0) return;
const channels = ensureRecord(raw, "channels");
for (const key of legacyEntries) {
const legacy = getRecord(raw[key]);
if (!legacy) continue;
const channelEntry = ensureRecord(channels, key);
const hadEntries = Object.keys(channelEntry).length > 0;
mergeMissing(channelEntry, legacy);
channels[key] = channelEntry;
delete raw[key];
changes.push(hadEntries ? `Merged ${key} → channels.${key}.` : `Moved ${key} → channels.${key}.`);
}
raw.channels = channels;
}
},
{
id: "routing.allowFrom->channels.whatsapp.allowFrom",
describe: "Move routing.allowFrom to channels.whatsapp.allowFrom",
apply: (raw, changes) => {
const routing = raw.routing;
if (!routing || typeof routing !== "object") return;
const allowFrom = routing.allowFrom;
if (allowFrom === void 0) return;
const channels = getRecord(raw.channels);
const whatsapp = channels ? getRecord(channels.whatsapp) : null;
if (!whatsapp) {
delete routing.allowFrom;
if (Object.keys(routing).length === 0) delete raw.routing;
changes.push("Removed routing.allowFrom (channels.whatsapp not configured).");
return;
}
if (whatsapp.allowFrom === void 0) {
whatsapp.allowFrom = allowFrom;
changes.push("Moved routing.allowFrom → channels.whatsapp.allowFrom.");
} else changes.push("Removed routing.allowFrom (channels.whatsapp.allowFrom already set).");
delete routing.allowFrom;
if (Object.keys(routing).length === 0) delete raw.routing;
channels.whatsapp = whatsapp;
raw.channels = channels;
}
},
{
id: "routing.groupChat.requireMention->groups.*.requireMention",
describe: "Move routing.groupChat.requireMention to channels.whatsapp/telegram/imessage groups",
apply: (raw, changes) => {
const routing = raw.routing;
if (!routing || typeof routing !== "object") return;
const groupChat = routing.groupChat && typeof routing.groupChat === "object" ? routing.groupChat : null;
if (!groupChat) return;
const requireMention = groupChat.requireMention;
if (requireMention === void 0) return;
const channels = ensureRecord(raw, "channels");
const applyTo = (key, options) => {
if (options?.requireExisting && !isRecord$1(channels[key])) return;
const section = channels[key] && typeof channels[key] === "object" ? channels[key] : {};
const groups = section.groups && typeof section.groups === "object" ? section.groups : {};
const defaultKey = "*";
const entry = groups[defaultKey] && typeof groups[defaultKey] === "object" ? groups[defaultKey] : {};
if (entry.requireMention === void 0) {
entry.requireMention = requireMention;
groups[defaultKey] = entry;
section.groups = groups;
channels[key] = section;
changes.push(`Moved routing.groupChat.requireMention → channels.${key}.groups."*".requireMention.`);
} else changes.push(`Removed routing.groupChat.requireMention (channels.${key}.groups."*" already set).`);
};
applyTo("whatsapp", { requireExisting: true });
applyTo("telegram");
applyTo("imessage");
delete groupChat.requireMention;
if (Object.keys(groupChat).length === 0) delete routing.groupChat;
if (Object.keys(routing).length === 0) delete raw.routing;
raw.channels = channels;
}
},
{
id: "gateway.token->gateway.auth.token",
describe: "Move gateway.token to gateway.auth.token",
apply: (raw, changes) => {
const gateway = raw.gateway;
if (!gateway || typeof gateway !== "object") return;
const token = gateway.token;
if (token === void 0) return;
const gatewayObj = gateway;
const auth = gatewayObj.auth && typeof gatewayObj.auth === "object" ? gatewayObj.auth : {};
if (auth.token === void 0) {
auth.token = token;
if (!auth.mode) auth.mode = "token";
changes.push("Moved gateway.token → gateway.auth.token.");
} else changes.push("Removed gateway.token (gateway.auth.token already set).");
delete gatewayObj.token;
if (Object.keys(auth).length > 0) gatewayObj.auth = auth;
raw.gateway = gatewayObj;
}
},
{
id: "telegram.requireMention->channels.telegram.groups.*.requireMention",
describe: "Move telegram.requireMention to channels.telegram.groups.*.requireMention",
apply: (raw, changes) => {
const channels = ensureRecord(raw, "channels");
const telegram = channels.telegram;
if (!telegram || typeof telegram !== "object") return;
const requireMention = telegram.requireMention;
if (requireMention === void 0) return;
const groups = telegram.groups && typeof telegram.groups === "object" ? telegram.groups : {};
const defaultKey = "*";
const entry = groups[defaultKey] && typeof groups[defaultKey] === "object" ? groups[defaultKey] : {};
if (entry.requireMention === void 0) {
entry.requireMention = requireMention;
groups[defaultKey] = entry;
telegram.groups = groups;
changes.push("Moved telegram.requireMention → channels.telegram.groups.\"*\".requireMention.");
} else changes.push("Removed telegram.requireMention (channels.telegram.groups.\"*\" already set).");
delete telegram.requireMention;
channels.telegram = telegram;
raw.channels = channels;
}
}
];
//#endregion
//#region src/config/legacy.migrations.part-2.ts
const LEGACY_CONFIG_MIGRATIONS_PART_2 = [
{
id: "agent.model-config-v2",
describe: "Migrate legacy agent.model/allowedModels/modelAliases/modelFallbacks/imageModelFallbacks to agent.models + model lists",
apply: (raw, changes) => {
const agentRoot = getRecord(raw.agent);
const defaults = getRecord(getRecord(raw.agents)?.defaults);
const agent = agentRoot ?? defaults;
if (!agent) return;
const label = agentRoot ? "agent" : "agents.defaults";
const legacyModel = typeof agent.model === "string" ? String(agent.model) : void 0;
const legacyImageModel = typeof agent.imageModel === "string" ? String(agent.imageModel) : void 0;
const legacyAllowed = Array.isArray(agent.allowedModels) ? agent.allowedModels.map(String) : [];
const legacyModelFallbacks = Array.isArray(agent.modelFallbacks) ? agent.modelFallbacks.map(String) : [];
const legacyImageModelFallbacks = Array.isArray(agent.imageModelFallbacks) ? agent.imageModelFallbacks.map(String) : [];
const legacyAliases = agent.modelAliases && typeof agent.modelAliases === "object" ? agent.modelAliases : {};
if (!(legacyModel || legacyImageModel || legacyAllowed.length > 0 || legacyModelFallbacks.length > 0 || legacyImageModelFallbacks.length > 0 || Object.keys(legacyAliases).length > 0)) return;
const models = agent.models && typeof agent.models === "object" ? agent.models : {};
const ensureModel = (rawKey) => {
if (typeof rawKey !== "string") return;
const key = rawKey.trim();
if (!key) return;
if (!models[key]) models[key] = {};
};
ensureModel(legacyModel);
ensureModel(legacyImageModel);
for (const key of legacyAllowed) ensureModel(key);
for (const key of legacyModelFallbacks) ensureModel(key);
for (const key of legacyImageModelFallbacks) ensureModel(key);
for (const target of Object.values(legacyAliases)) {
if (typeof target !== "string") continue;
ensureModel(target);
}
for (const [alias, targetRaw] of Object.entries(legacyAliases)) {
if (typeof targetRaw !== "string") continue;
const target = targetRaw.trim();
if (!target) continue;
const entry = models[target] && typeof models[target] === "object" ? models[target] : {};
if (!("alias" in entry)) {
entry.alias = alias;
models[target] = entry;
}
}
const currentModel = agent.model && typeof agent.model === "object" ? agent.model : null;
if (currentModel) {
if (!currentModel.primary && legacyModel) currentModel.primary = legacyModel;
if (legacyModelFallbacks.length > 0 && (!Array.isArray(currentModel.fallbacks) || currentModel.fallbacks.length === 0)) currentModel.fallbacks = legacyModelFallbacks;
agent.model = currentModel;
} else if (legacyModel || legacyModelFallbacks.length > 0) agent.model = {
primary: legacyModel,
fallbacks: legacyModelFallbacks.length ? legacyModelFallbacks : []
};
const currentImageModel = agent.imageModel && typeof agent.imageModel === "object" ? agent.imageModel : null;
if (currentImageModel) {
if (!currentImageModel.primary && legacyImageModel) currentImageModel.primary = legacyImageModel;
if (legacyImageModelFallbacks.length > 0 && (!Array.isArray(currentImageModel.fallbacks) || currentImageModel.fallbacks.length === 0)) currentImageModel.fallbacks = legacyImageModelFallbacks;
agent.imageModel = currentImageModel;
} else if (legacyImageModel || legacyImageModelFallbacks.length > 0) agent.imageModel = {
primary: legacyImageModel,
fallbacks: legacyImageModelFallbacks.length ? legacyImageModelFallbacks : []
};
agent.models = models;
if (legacyModel !== void 0) changes.push(`Migrated ${label}.model string → ${label}.model.primary.`);
if (legacyModelFallbacks.length > 0) changes.push(`Migrated ${label}.modelFallbacks → ${label}.model.fallbacks.`);
if (legacyImageModel !== void 0) changes.push(`Migrated ${label}.imageModel string → ${label}.imageModel.primary.`);
if (legacyImageModelFallbacks.length > 0) changes.push(`Migrated ${label}.imageModelFallbacks → ${label}.imageModel.fallbacks.`);
if (legacyAllowed.length > 0) changes.push(`Migrated ${label}.allowedModels → ${label}.models.`);
if (Object.keys(legacyAliases).length > 0) changes.push(`Migrated ${label}.modelAliases → ${label}.models.*.alias.`);
delete agent.allowedModels;
delete agent.modelAliases;
delete agent.modelFallbacks;
delete agent.imageModelFallbacks;
}
},
{
id: "routing.agents-v2",
describe: "Move routing.agents/defaultAgentId to agents.list",
apply: (raw, changes) => {
const routing = getRecord(raw.routing);
if (!routing) return;
const routingAgents = getRecord(routing.agents);
const agents = ensureRecord(raw, "agents");
const list = getAgentsList(agents);
if (routingAgents) {
for (const [rawId, entryRaw] of Object.entries(routingAgents)) {
const agentId = String(rawId ?? "").trim();
const entry = getRecord(entryRaw);
if (!agentId || !entry) continue;
const target = ensureAgentEntry(list, agentId);
const entryCopy = { ...entry };
if ("mentionPatterns" in entryCopy) {
const mentionPatterns = entryCopy.mentionPatterns;
const groupChat = ensureRecord(target, "groupChat");
if (groupChat.mentionPatterns === void 0) {
groupChat.mentionPatterns = mentionPatterns;
changes.push(`Moved routing.agents.${agentId}.mentionPatterns → agents.list (id "${agentId}").groupChat.mentionPatterns.`);
} else changes.push(`Removed routing.agents.${agentId}.mentionPatterns (agents.list groupChat mentionPatterns already set).`);
delete entryCopy.mentionPatterns;
}
const legacyGroupChat = getRecord(entryCopy.groupChat);
if (legacyGroupChat) {
mergeMissing(ensureRecord(target, "groupChat"), legacyGroupChat);
delete entryCopy.groupChat;
}
const legacySandbox = getRecord(entryCopy.sandbox);
if (legacySandbox) {
const sandboxTools = getRecord(legacySandbox.tools);
if (sandboxTools) {
mergeMissing(ensureRecord(ensureRecord(ensureRecord(target, "tools"), "sandbox"), "tools"), sandboxTools);
delete legacySandbox.tools;
changes.push(`Moved routing.agents.${agentId}.sandbox.tools → agents.list (id "${agentId}").tools.sandbox.tools.`);
}
entryCopy.sandbox = legacySandbox;
}
mergeMissing(target, entryCopy);
}
delete routing.agents;
changes.push("Moved routing.agents → agents.list.");
}
const defaultAgentId = typeof routing.defaultAgentId === "string" ? routing.defaultAgentId.trim() : "";
if (defaultAgentId) {
if (!list.some((entry) => isRecord$1(entry) && entry.default === true)) {
const entry = ensureAgentEntry(list, defaultAgentId);
entry.default = true;
changes.push(`Moved routing.defaultAgentId → agents.list (id "${defaultAgentId}").default.`);
} else changes.push("Removed routing.defaultAgentId (agents.list default already set).");
delete routing.defaultAgentId;
}
if (list.length > 0) agents.list = list;
if (Object.keys(routing).length === 0) delete raw.routing;
}
},
{
id: "routing.config-v2",
describe: "Move routing bindings/groupChat/queue/agentToAgent/transcribeAudio",
apply: (raw, changes) => {
const routing = getRecord(raw.routing);
if (!routing) return;
if (routing.bindings !== void 0) {
if (raw.bindings === void 0) {
raw.bindings = routing.bindings;
changes.push("Moved routing.bindings → bindings.");
} else changes.push("Removed routing.bindings (bindings already set).");
delete routing.bindings;
}
if (routing.agentToAgent !== void 0) {
const tools = ensureRecord(raw, "tools");
if (tools.agentToAgent === void 0) {
tools.agentToAgent = routing.agentToAgent;
changes.push("Moved routing.agentToAgent → tools.agentToAgent.");
} else changes.push("Removed routing.agentToAgent (tools.agentToAgent already set).");
delete routing.agentToAgent;
}
if (routing.queue !== void 0) {
const messages = ensureRecord(raw, "messages");
if (messages.queue === void 0) {
messages.queue = routing.queue;
changes.push("Moved routing.queue → messages.queue.");
} else changes.push("Removed routing.queue (messages.queue already set).");
delete routing.queue;
}
const groupChat = getRecord(routing.groupChat);
if (groupChat) {
const historyLimit = groupChat.historyLimit;
if (historyLimit !== void 0) {
const messagesGroup = ensureRecord(ensureRecord(raw, "messages"), "groupChat");
if (messagesGroup.historyLimit === void 0) {
messagesGroup.historyLimit = historyLimit;
changes.push("Moved routing.groupChat.historyLimit → messages.groupChat.historyLimit.");
} else changes.push("Removed routing.groupChat.historyLimit (messages.groupChat.historyLimit already set).");
delete groupChat.historyLimit;
}
const mentionPatterns = groupChat.mentionPatterns;
if (mentionPatterns !== void 0) {
const messagesGroup = ensureRecord(ensureRecord(raw, "messages"), "groupChat");
if (messagesGroup.mentionPatterns === void 0) {
messagesGroup.mentionPatterns = mentionPatterns;
changes.push("Moved routing.groupChat.mentionPatterns → messages.groupChat.mentionPatterns.");
} else changes.push("Removed routing.groupChat.mentionPatterns (messages.groupChat.mentionPatterns already set).");
delete groupChat.mentionPatterns;
}
if (Object.keys(groupChat).length === 0) delete routing.groupChat;
else routing.groupChat = groupChat;
}
if (routing.transcribeAudio !== void 0) {
const mapped = mapLegacyAudioTranscription(routing.transcribeAudio);
if (mapped) {
const mediaAudio = ensureRecord(ensureRecord(ensureRecord(raw, "tools"), "media"), "audio");
if ((Array.isArray(mediaAudio.models) ? mediaAudio.models : []).length === 0) {
mediaAudio.enabled = true;
mediaAudio.models = [mapped];
changes.push("Moved routing.transcribeAudio → tools.media.audio.models.");
} else changes.push("Removed routing.transcribeAudio (tools.media.audio.models already set).");
} else changes.push("Removed routing.transcribeAudio (unsupported transcription CLI).");
delete routing.transcribeAudio;
}
const audio = getRecord(raw.audio);
if (audio?.transcription !== void 0) {
const mapped = mapLegacyAudioTranscription(audio.transcription);
if (mapped) {
const mediaAudio = ensureRecord(ensureRecord(ensureRecord(raw, "tools"), "media"), "audio");
if ((Array.isArray(mediaAudio.models) ? mediaAudio.models : []).length === 0) {
mediaAudio.enabled = true;
mediaAudio.models = [mapped];
changes.push("Moved audio.transcription → tools.media.audio.models.");
} else changes.push("Removed audio.transcription (tools.media.audio.models already set).");
delete audio.transcription;
if (Object.keys(audio).length === 0) delete raw.audio;
else raw.audio = audio;
} else {
delete audio.transcription;
changes.push("Removed audio.transcription (unsupported transcription CLI).");
if (Object.keys(audio).length === 0) delete raw.audio;
else raw.audio = audio;
}
}
if (Object.keys(routing).length === 0) delete raw.routing;
}
}
];
//#endregion
//#region src/config/legacy.migrations.part-3.ts
const LEGACY_CONFIG_MIGRATIONS_PART_3 = [
{
id: "auth.anthropic-claude-cli-mode-oauth",
describe: "Switch anthropic:claude-cli auth profile mode to oauth",
apply: (raw, changes) => {
const profiles = getRecord(getRecord(raw.auth)?.profiles);
if (!profiles) return;
const claudeCli = getRecord(profiles["anthropic:claude-cli"]);
if (!claudeCli) return;
if (claudeCli.mode !== "token") return;
claudeCli.mode = "oauth";
changes.push("Updated auth.profiles[\"anthropic:claude-cli\"].mode → \"oauth\".");
}
},
{
id: "tools.bash->tools.exec",
describe: "Move tools.bash to tools.exec",
apply: (raw, changes) => {
const tools = ensureRecord(raw, "tools");
const bash = getRecord(tools.bash);
if (!bash) return;
if (tools.exec === void 0) {
tools.exec = bash;
changes.push("Moved tools.bash → tools.exec.");
} else changes.push("Removed tools.bash (tools.exec already set).");
delete tools.bash;
}
},
{
id: "messages.tts.enabled->auto",
describe: "Move messages.tts.enabled to messages.tts.auto",
apply: (raw, changes) => {
const tts = getRecord(getRecord(raw.messages)?.tts);
if (!tts) return;
if (tts.auto !== void 0) {
if ("enabled" in tts) {
delete tts.enabled;
changes.push("Removed messages.tts.enabled (messages.tts.auto already set).");
}
return;
}
if (typeof tts.enabled !== "boolean") return;
tts.auto = tts.enabled ? "always" : "off";
delete tts.enabled;
changes.push(`Moved messages.tts.enabled → messages.tts.auto (${String(tts.auto)}).`);
}
},
{
id: "agent.defaults-v2",
describe: "Move agent config to agents.defaults and tools",
apply: (raw, changes) => {
const agent = getRecord(raw.agent);
if (!agent) return;
const agents = ensureRecord(raw, "agents");
const defaults = getRecord(agents.defaults) ?? {};
const tools = ensureRecord(raw, "tools");
const agentTools = getRecord(agent.tools);
if (agentTools) {
if (tools.allow === void 0 && agentTools.allow !== void 0) {
tools.allow = agentTools.allow;
changes.push("Moved agent.tools.allow → tools.allow.");
}
if (tools.deny === void 0 && age