openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
197 lines (196 loc) • 9.32 kB
JavaScript
import { c as normalizeOptionalLowercaseString } from "./string-coerce-CIXf7egm.js";
import { s as readConfigFileSnapshot } from "./io.runtime-B9iJRs3w.js";
import { C as tryResolveAgentOperationAgentId, _ as resolveConfiguredAgentId } from "./agent-scope-config-DcbEhP0R.js";
import "./session-key-BnWWjqNc.js";
import { n as normalizeAccountId } from "./account-id-CETVCrTz.js";
import { n as resolveChannelSetupExecutionAdapter } from "./setup-contract-RbJSZiOH.js";
import "./config-Cs0XXL3x.js";
import { c as formatUnknownChannelMessage } from "./error-format-Cae7EwnT.js";
import { n as getLoadedChannelPlugin, t as getChannelPlugin } from "./registry-Cz6cv4VC.js";
import "./plugins-Cozj1Enf.js";
import { t as describeBinding } from "./agents.binding-format-BRYI5aWJ.js";
import { t as applyAgentBindings } from "./agents.bindings-xbGQEjU6.js";
import { t as commitConfigWithPendingPluginInstalls } from "./install-record-commit-C4pioTsf.js";
import { n as refreshPluginRegistryAfterConfigMutation } from "./registry-refresh-CvO7IUdQ.js";
import { t as resolveChannelSetupOwner } from "./owner-nhq5CyLg.js";
//#region src/commands/channels/add-mutators.ts
/** Apply a display name to a channel account when the plugin supports account naming. */
function applyAccountName(params) {
const accountId = normalizeAccountId(params.accountId);
const plugin = params.plugin ?? getChannelPlugin(params.channel);
const apply = plugin ? resolveChannelSetupExecutionAdapter(plugin)?.applyAccountName : void 0;
return apply ? apply({
cfg: params.cfg,
accountId,
name: params.name
}) : params.cfg;
}
//#endregion
//#region src/commands/channels/add-wizard.ts
function unresolvedInitialWizardChannelTarget(channel) {
return {
kind: "unresolved",
message: formatUnknownChannelMessage({ channel })
};
}
/** Resolve omitted, matched, and unmatched channel targets without collapsing caller intent. */
async function resolveInitialWizardChannelTarget(raw, cfg, workspaceDir) {
if (raw === void 0) return { kind: "omitted" };
const normalized = normalizeOptionalLowercaseString(raw);
if (!normalized) return unresolvedInitialWizardChannelTarget("");
const [{ listActiveChannelSetupPlugins }, { resolveChannelSetupEntries }] = await Promise.all([import("./setup-registry-DhHIpF5J.js"), import("./discovery-DehDhH5q.js")]);
const resolved = resolveChannelSetupEntries({
cfg,
installedPlugins: listActiveChannelSetupPlugins(),
workspaceDir: workspaceDir ?? resolveChannelSetupOwner(cfg).workspaceDir
});
const matchedEntry = resolved.entries.find((candidate) => normalizeOptionalLowercaseString(candidate.id) === normalized) ?? resolved.entries.find((candidate) => (candidate.meta.aliases ?? []).some((alias) => normalizeOptionalLowercaseString(alias) === normalized));
return matchedEntry ? {
kind: "resolved",
channel: matchedEntry.id
} : unresolvedInitialWizardChannelTarget(raw.trim());
}
/** Run the interactive channel-setup flow and persist the resulting config. */
async function runChannelsAddWizardFlow(params) {
const { cfg, baseHash, runtime, prompter } = params;
const [{ buildAgentSummaries }, onboardChannels] = await Promise.all([import("./agents.config-EkjdV_7g.js"), import("./onboard-channels-wf3-q-wO.js")]);
const channelSetup = onboardChannels.createChannelSetupTransaction({
runtime,
...params.beforePersistentEffect ? { beforePersistentEffect: params.beforePersistentEffect } : {}
});
let selection = [];
const accountIds = {};
const resolvedPlugins = /* @__PURE__ */ new Map();
await prompter.intro("Channel setup");
let nextConfig = await onboardChannels.setupChannels(cfg, runtime, prompter, {
...params.workspaceDir ? { workspaceDir: params.workspaceDir } : {},
...params.initialChannel ? { initialSelection: [params.initialChannel] } : {},
...params.initialChannel ? { finishAfterInitialSelection: true } : {},
allowDisable: false,
allowIMessageInstall: true,
allowSignalInstall: true,
...params.beforePersistentEffect ? { beforePersistentEffect: params.beforePersistentEffect } : {},
...params.deferDeviceLinkToClient ? { deferDeviceLinkToClient: true } : {},
onPostWriteHook: (hook) => channelSetup.onPostWriteHook(hook),
promptAccountIds: true,
deferStatusUntilSelection: true,
skipStatusNote: true,
onSelection: (value) => {
selection = value;
},
onAccountId: (channel, accountId) => {
accountIds[channel] = accountId;
},
onResolvedPlugin: (channel, plugin) => {
resolvedPlugins.set(channel, plugin);
}
});
const commitWizardConfig = async (config) => {
return await channelSetup.commit(config, async (configToCommit) => {
const committed = await commitConfigWithPendingPluginInstalls({
nextConfig: configToCommit,
...baseHash !== void 0 ? { baseHash } : {}
});
if (committed.movedInstallRecords) await refreshPluginRegistryAfterConfigMutation({
config: committed.config,
reason: "source-changed",
installRecords: committed.installRecords,
logger: { warn: (message) => runtime.log(message) }
});
return committed.config;
});
};
if (selection.length === 0) {
if (nextConfig !== cfg) {
await commitWizardConfig(nextConfig);
await prompter.outro("Channels updated.");
return;
}
await prompter.outro("No channel changes made.");
return;
}
const usesTargetedDefaults = params.initialChannel !== void 0 && selection.length === 1 && selection[0] === params.initialChannel;
if (usesTargetedDefaults ? false : await prompter.confirm({
message: "Name these channel accounts now? (optional)",
initialValue: false
})) for (const channel of selection) {
const accountId = accountIds[channel] ?? "default";
const plugin = resolvedPlugins.get(channel) ?? getLoadedChannelPlugin(channel);
const account = plugin?.config.resolveAccount(nextConfig, accountId);
const existingName = (plugin?.config.describeAccount?.(account, nextConfig))?.name ?? account?.name;
const name = await prompter.text({
message: `${channel} display name for account "${accountId}"`,
initialValue: existingName
});
if (name?.trim()) nextConfig = applyAccountName({
cfg: nextConfig,
channel,
accountId,
name,
plugin
});
}
const bindTargets = selection.map((channel) => ({
channel,
accountId: accountIds[channel]?.trim()
})).filter((value) => Boolean(value.accountId));
if (bindTargets.length > 0) {
const agentSummaries = buildAgentSummaries(nextConfig);
if (usesTargetedDefaults && agentSummaries.length <= 1 ? false : usesTargetedDefaults ? true : await prompter.confirm({
message: "Route these channel accounts to agents now?",
initialValue: true
})) {
const owner = tryResolveAgentOperationAgentId(nextConfig);
const defaultAgentId = owner === void 0 ? void 0 : resolveConfiguredAgentId(nextConfig, owner);
for (const target of bindTargets) {
const targetAgentId = await prompter.select({
message: `Send ${target.channel}/${target.accountId} messages to agent`,
options: agentSummaries.map((agent) => ({
value: agent.id,
label: agent.isDefault ? `${agent.id} (default)` : agent.id
})),
initialValue: defaultAgentId
});
const bindingResult = applyAgentBindings(nextConfig, [{
agentId: targetAgentId,
match: {
channel: target.channel,
accountId: target.accountId
}
}]);
nextConfig = bindingResult.config;
if (bindingResult.added.length > 0 || bindingResult.updated.length > 0) await prompter.note([...bindingResult.added.map((binding) => `Added: ${describeBinding(binding)}`), ...bindingResult.updated.map((binding) => `Updated: ${describeBinding(binding)}`)].join("\n"), "Routing bindings");
if (bindingResult.conflicts.length > 0) await prompter.note(["Skipped bindings already claimed by another agent:", ...bindingResult.conflicts.map((conflict) => `- ${describeBinding(conflict.binding)} (agent=${conflict.existingAgentId})`)].join("\n"), "Routing bindings");
}
}
}
await commitWizardConfig(nextConfig);
params.onConfigured?.(selection.map((channel) => ({
channel,
accountId: accountIds[channel] ?? "default"
})));
await prompter.outro("Channels updated.");
}
/**
* Gateway entry for `wizard.start {flow:"channels"}`. Unlike the CLI path this
* must never call runtime.exit — failures throw and surface as wizard errors.
*/
async function runChannelsSetupWizard(opts, runtime, prompter) {
const snapshot = await readConfigFileSnapshot();
if (snapshot.exists && !snapshot.valid) throw new Error("OpenClaw config is invalid; run `openclaw doctor --fix`, then retry channel setup.");
const cfg = snapshot.sourceConfig ?? snapshot.config;
const target = await resolveInitialWizardChannelTarget(opts.channel, cfg);
if (target.kind === "unresolved") throw new Error(target.message);
await runChannelsAddWizardFlow({
cfg,
...snapshot.hash !== void 0 ? { baseHash: snapshot.hash } : {},
runtime,
prompter,
...target.kind === "resolved" ? { initialChannel: target.channel } : {},
deferDeviceLinkToClient: true,
...opts.onConfigured ? { onConfigured: opts.onConfigured } : {},
...opts.beforePersistentEffect ? { beforePersistentEffect: opts.beforePersistentEffect } : {}
});
}
//#endregion
export { resolveInitialWizardChannelTarget, runChannelsAddWizardFlow, runChannelsSetupWizard };