openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
237 lines (236 loc) • 11.1 kB
JavaScript
import { c as normalizeOptionalLowercaseString } from "./string-coerce-6kR-86XC.mjs";
import { c as isRecord } from "./record-coerce-TIsquoVR.mjs";
import { S as tryResolveAgentOperationAgentId, m as resolveConfiguredAgentId, t as AgentSelectionRequiredError } from "./agent-scope-config-BU4qc9bA.mjs";
import "./session-key-BC4m_Ly5.mjs";
import { n as normalizeAccountId } from "./account-id-CW6dlabA.mjs";
import { i as listAgentIds } from "./agent-roster-B45JERT5.mjs";
import { n as resolveChannelSetupExecutionAdapter } from "./setup-contract-B8gsukg0.mjs";
import { p as readCurrentConfigForPolicyCheckAsync, u as readConfigFileSnapshotForWrite } from "./io.runtime-FunTy0y1.mjs";
import "./config-B-SAEPyK.mjs";
import { n as getLoadedChannelPlugin, t as getChannelPlugin } from "./registry-B56V4gqI.mjs";
import "./plugins-CBMHLsl7.mjs";
import { n as resolveChannelAccount } from "./account-resolution-DCHDXWZ0.mjs";
import { c as formatUnknownChannelMessage } from "./error-format-DiuKk5bW.mjs";
import { t as describeBinding } from "./agents.binding-format-BRYI5aWJ.mjs";
import { t as applyAgentBindings } from "./agents.bindings-DI0R2Y8j.mjs";
import { t as resolveChannelSetupOwner } from "./owner-Czo7pO7R.mjs";
import { i as withCommandPluginMetadata } from "./config-validation-DuIBaLJF.mjs";
import { t as persistChannelPluginConfig } from "./plugin-config-persistence-BjUZWo3K.mjs";
//#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 })
};
}
/** Select a setup owner before workspace-scoped channel discovery. */
async function selectChannelSetupOwner(writeSnapshot, prompter, requestedAgentId) {
const cfg = writeSnapshot.snapshot.sourceConfig;
try {
return resolveChannelSetupOwner(cfg, requestedAgentId);
} catch (error) {
if (!(error instanceof AgentSelectionRequiredError)) throw error;
}
const selectedAgent = await prompter.select({
message: "Set up channels for agent",
options: listAgentIds(cfg).map((agentId) => ({
value: { agentId },
label: agentId
}))
});
if (!isRecord(selectedAgent) || typeof selectedAgent.agentId !== "string") throw new Error("Invalid channel setup owner selection");
writeSnapshot.writeOptions.assertConfigPathForWrite?.();
const currentConfig = await readCurrentConfigForPolicyCheckAsync({
configPath: writeSnapshot.snapshot.path,
env: process.env
});
writeSnapshot.writeOptions.assertConfigPathForWrite?.();
const agentId = resolveConfiguredAgentId(currentConfig, selectedAgent.agentId);
return resolveChannelSetupOwner(currentConfig, agentId);
}
/** 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-BwMzt3Dz.mjs"), import("./discovery-sAdc9xNX.mjs")]);
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 { writeSnapshot, runtime, prompter } = params;
const { sourceConfig: cfg, hash: baseHash } = writeSnapshot.snapshot;
const [{ buildAgentSummaries }, onboardChannels] = await Promise.all([import("./agents.config-CKTAbYl9.mjs"), import("./channel-setup-CUN4u2TA.mjs")]);
const channelSetup = onboardChannels.createChannelSetupHooks({
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.assertPersistentEffectCurrent ? { assertPersistentEffectCurrent: params.assertPersistentEffectCurrent } : {},
...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) => {
await params.beforePersistentEffect?.();
const committed = await persistChannelPluginConfig({
cfg: config,
pluginInstalled: false,
writeOptions: writeSnapshot.writeOptions,
baseHash,
runtime
});
await channelSetup.runPostWriteHooks(committed.path);
return committed.nextConfig;
};
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
})) await withCommandPluginMetadata({
config: nextConfig,
workspaceDir: params.workspaceDir
}, async () => {
for (const channel of selection) {
const accountId = accountIds[channel] ?? "default";
const plugin = resolvedPlugins.get(channel) ?? getLoadedChannelPlugin(channel);
const account = plugin ? await resolveChannelAccount({
plugin,
cfg: nextConfig,
accountId
}) : void 0;
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 = await 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: params.agentId ?? 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 writeSnapshot = await readConfigFileSnapshotForWrite();
const { snapshot } = writeSnapshot;
if (snapshot.exists && !snapshot.valid) throw new Error("OpenClaw config is invalid; run `openclaw doctor --fix`, then retry channel setup.");
const cfg = snapshot.sourceConfig;
const { agentId, workspaceDir } = await selectChannelSetupOwner(writeSnapshot, prompter);
const target = await resolveInitialWizardChannelTarget(opts.channel, cfg, workspaceDir);
if (target.kind === "unresolved") throw new Error(target.message);
await runChannelsAddWizardFlow({
writeSnapshot,
agentId,
runtime,
prompter,
workspaceDir,
...target.kind === "resolved" ? { initialChannel: target.channel } : {},
deferDeviceLinkToClient: true,
...opts.onConfigured ? { onConfigured: opts.onConfigured } : {},
...opts.beforePersistentEffect ? { beforePersistentEffect: opts.beforePersistentEffect } : {},
...opts.assertPersistentEffectCurrent ? { assertPersistentEffectCurrent: opts.assertPersistentEffectCurrent } : {}
});
}
//#endregion
export { resolveInitialWizardChannelTarget, runChannelsAddWizardFlow, runChannelsSetupWizard, selectChannelSetupOwner };