mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-20 22:51:43 +00:00
Extract shared normalization/coercion helpers into private @openclaw/normalization-core workspace package while preserving existing plugin SDK helper subpaths.\n\nAlso keeps direct normalization-core imports internal, wires UI/build/loader resolution, and replaces the slow PR network CodeQL lane with a fast added-line boundary scan while retaining full CodeQL for scheduled/manual runs.\n\nVerification: local moved tests, plugin SDK boundary tests, extension loader tests, agents-support shard, UI build/test, build artifacts, lint, workflow guards, autoreview, and GitHub CI passed on PR head 963d893715.
135 lines
4.5 KiB
TypeScript
135 lines
4.5 KiB
TypeScript
import {
|
|
normalizeLowercaseStringOrEmpty,
|
|
normalizeOptionalString,
|
|
} from "@openclaw/normalization-core/string-coerce";
|
|
import {
|
|
GATEWAY_CLIENT_MODES,
|
|
GATEWAY_CLIENT_NAMES,
|
|
} from "../../packages/gateway-protocol/src/client-info.js";
|
|
import { resolveDefaultAgentId } from "../agents/agent-scope.js";
|
|
import { CHANNEL_MESSAGE_ACTION_NAMES } from "../channels/plugins/message-action-names.js";
|
|
import type { ChannelMessageActionName } from "../channels/plugins/types.public.js";
|
|
import { resolveCommandConfigWithSecrets } from "../cli/command-config-resolution.js";
|
|
import { formatCliCommand } from "../cli/command-format.js";
|
|
import { getScopedChannelsCommandSecretTargets } from "../cli/command-secret-targets.js";
|
|
import { resolveMessageSecretScope } from "../cli/message-secret-scope.js";
|
|
import { createOutboundSendDeps, type CliDeps } from "../cli/outbound-send-deps.js";
|
|
import { withProgress } from "../cli/progress.js";
|
|
import { getRuntimeConfig } from "../config/config.js";
|
|
import type { OutboundSendDeps } from "../infra/outbound/deliver.js";
|
|
import { runMessageAction } from "../infra/outbound/message-action-runner.js";
|
|
import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js";
|
|
|
|
function extractMessageId(payload: unknown): string | undefined {
|
|
if (!payload || typeof payload !== "object") {
|
|
return undefined;
|
|
}
|
|
const record = payload as Record<string, unknown>;
|
|
const direct = normalizeOptionalString(record.messageId);
|
|
if (direct) {
|
|
return direct;
|
|
}
|
|
const result = record.result;
|
|
if (result && typeof result === "object") {
|
|
const nested = normalizeOptionalString((result as Record<string, unknown>).messageId);
|
|
if (nested) {
|
|
return nested;
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function buildMessageCliJson(result: Awaited<ReturnType<typeof runMessageAction>>) {
|
|
const messageId = extractMessageId(result.payload);
|
|
return {
|
|
action: result.action,
|
|
channel: result.channel,
|
|
dryRun: result.dryRun,
|
|
handledBy: result.handledBy,
|
|
...(messageId ? { messageId } : {}),
|
|
payload: result.payload,
|
|
};
|
|
}
|
|
|
|
export async function messageCommand(
|
|
opts: Record<string, unknown>,
|
|
deps: CliDeps,
|
|
runtime: RuntimeEnv,
|
|
) {
|
|
const loadedRaw = getRuntimeConfig();
|
|
const scope = resolveMessageSecretScope({
|
|
channel: opts.channel,
|
|
target: opts.target,
|
|
targets: opts.targets,
|
|
accountId: opts.accountId,
|
|
});
|
|
const scopedTargets = getScopedChannelsCommandSecretTargets({
|
|
config: loadedRaw,
|
|
channel: scope.channel,
|
|
accountId: scope.accountId,
|
|
});
|
|
const { effectiveConfig: cfg } = await resolveCommandConfigWithSecrets({
|
|
config: loadedRaw,
|
|
commandName: "message",
|
|
targetIds: scopedTargets.targetIds,
|
|
...(scopedTargets.allowedPaths ? { allowedPaths: scopedTargets.allowedPaths } : {}),
|
|
runtime,
|
|
autoEnable: true,
|
|
});
|
|
const rawAction = normalizeOptionalString(opts.action) ?? "";
|
|
const actionInput = rawAction || "send";
|
|
const normalizedActionInput = normalizeLowercaseStringOrEmpty(actionInput);
|
|
const actionMatch = (CHANNEL_MESSAGE_ACTION_NAMES as readonly string[]).find(
|
|
(name) => normalizeLowercaseStringOrEmpty(name) === normalizedActionInput,
|
|
);
|
|
if (!actionMatch) {
|
|
throw new Error(
|
|
`Unknown message action "${actionInput}". Use one of ${CHANNEL_MESSAGE_ACTION_NAMES.join(
|
|
", ",
|
|
)}. Example: ${formatCliCommand("openclaw message send --channel <channel> --target <id> --text <message>")}.`,
|
|
);
|
|
}
|
|
const action = actionMatch as ChannelMessageActionName;
|
|
|
|
const outboundDeps: OutboundSendDeps = createOutboundSendDeps(deps);
|
|
|
|
const run = async () =>
|
|
await runMessageAction({
|
|
cfg,
|
|
action,
|
|
params: opts,
|
|
deps: outboundDeps,
|
|
agentId: resolveDefaultAgentId(cfg),
|
|
senderIsOwner: opts.senderIsOwner !== false,
|
|
gateway: {
|
|
clientName: GATEWAY_CLIENT_NAMES.CLI,
|
|
mode: GATEWAY_CLIENT_MODES.CLI,
|
|
},
|
|
});
|
|
|
|
const json = opts.json === true;
|
|
const dryRun = opts.dryRun === true;
|
|
const needsSpinner = !json && !dryRun && (action === "send" || action === "poll");
|
|
|
|
const result = needsSpinner
|
|
? await withProgress(
|
|
{
|
|
label: action === "poll" ? "Sending poll..." : "Sending...",
|
|
indeterminate: true,
|
|
enabled: true,
|
|
},
|
|
run,
|
|
)
|
|
: await run();
|
|
|
|
if (json) {
|
|
writeRuntimeJson(runtime, buildMessageCliJson(result));
|
|
return;
|
|
}
|
|
|
|
const { formatMessageCliText } = await import("./message-format.js");
|
|
for (const line of formatMessageCliText(result)) {
|
|
runtime.log(line);
|
|
}
|
|
}
|