Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
427
scripts/e2e/lib/release-user-journey/write-clickclack-plugin.mjs
Normal file
427
scripts/e2e/lib/release-user-journey/write-clickclack-plugin.mjs
Normal file
@@ -0,0 +1,427 @@
|
||||
#!/usr/bin/env node
|
||||
// Writes the external ClickClack channel fixture used by release journey E2Es.
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
const pluginDir = process.argv[2];
|
||||
if (!pluginDir) {
|
||||
console.error("usage: write-clickclack-plugin.mjs <plugin-dir>");
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
fs.mkdirSync(pluginDir, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(pluginDir, "package.json"),
|
||||
`${JSON.stringify(
|
||||
{
|
||||
name: "clickclack",
|
||||
version: "0.0.1",
|
||||
type: "module",
|
||||
openclaw: { extensions: ["./index.mjs"] },
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}\n`,
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(pluginDir, "openclaw.plugin.json"),
|
||||
`${JSON.stringify(
|
||||
{
|
||||
id: "clickclack",
|
||||
activation: { onStartup: false },
|
||||
channels: ["clickclack"],
|
||||
channelEnvVars: { clickclack: ["CLICKCLACK_BOT_TOKEN"] },
|
||||
channelConfigs: {
|
||||
clickclack: {
|
||||
schema: {
|
||||
type: "object",
|
||||
additionalProperties: true,
|
||||
properties: {
|
||||
enabled: { type: "boolean", default: true },
|
||||
baseUrl: { type: "string" },
|
||||
workspace: { type: "string" },
|
||||
defaultTo: { type: "string" },
|
||||
token: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
configSchema: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}\n`,
|
||||
);
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(pluginDir, "index.mjs"),
|
||||
`import crypto from "node:crypto";
|
||||
import net from "node:net";
|
||||
|
||||
const CHANNEL_ID = "clickclack";
|
||||
const DEFAULT_ACCOUNT_ID = "default";
|
||||
|
||||
function configFor(cfg) {
|
||||
return cfg?.channels?.clickclack ?? {};
|
||||
}
|
||||
|
||||
function readToken(raw) {
|
||||
if (typeof raw === "string") {
|
||||
return raw.trim();
|
||||
}
|
||||
if (raw && typeof raw === "object" && raw.source === "env" && typeof raw.id === "string") {
|
||||
return String(process.env[raw.id] ?? "").trim();
|
||||
}
|
||||
return String(process.env.CLICKCLACK_BOT_TOKEN ?? "").trim();
|
||||
}
|
||||
|
||||
function resolveAccount(cfg, accountId = DEFAULT_ACCOUNT_ID) {
|
||||
const config = configFor(cfg);
|
||||
const token = readToken(config.token);
|
||||
const baseUrl = typeof config.baseUrl === "string" ? config.baseUrl : "";
|
||||
return {
|
||||
accountId: accountId ?? DEFAULT_ACCOUNT_ID,
|
||||
enabled: config.enabled !== false,
|
||||
configured: Boolean(baseUrl && token),
|
||||
baseUrl,
|
||||
token,
|
||||
workspace: typeof config.workspace === "string" && config.workspace ? config.workspace : "release",
|
||||
defaultTo: typeof config.defaultTo === "string" ? config.defaultTo : "channel:general",
|
||||
reconnectMs: Number.isFinite(config.reconnectMs) ? Math.max(50, Number(config.reconnectMs)) : 250,
|
||||
};
|
||||
}
|
||||
|
||||
async function requestJson(account, method, pathname, body) {
|
||||
const response = await fetch(new URL(pathname, account.baseUrl), {
|
||||
method,
|
||||
headers: {
|
||||
authorization: \`Bearer \${account.token}\`,
|
||||
...(body == null ? {} : { "content-type": "application/json" }),
|
||||
},
|
||||
...(body == null ? {} : { body: JSON.stringify(body) }),
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(\`ClickClack fixture \${response.status}: \${await response.text()}\`);
|
||||
}
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
async function resolveWorkspaceId(account) {
|
||||
const data = await requestJson(account, "GET", "/api/workspaces");
|
||||
const workspaces = Array.isArray(data.workspaces) ? data.workspaces : [];
|
||||
const match = workspaces.find((workspace) =>
|
||||
workspace?.id === account.workspace ||
|
||||
workspace?.slug === account.workspace ||
|
||||
workspace?.name === account.workspace
|
||||
);
|
||||
if (!match?.id) {
|
||||
throw new Error(\`ClickClack workspace not found: \${account.workspace}\`);
|
||||
}
|
||||
return match.id;
|
||||
}
|
||||
|
||||
async function resolveChannelId(account, workspaceId, rawTarget) {
|
||||
const target = String(rawTarget ?? "").trim();
|
||||
const channelName = target.startsWith("channel:") ? target.slice("channel:".length) : target;
|
||||
const data = await requestJson(account, "GET", \`/api/workspaces/\${encodeURIComponent(workspaceId)}/channels\`);
|
||||
const channels = Array.isArray(data.channels) ? data.channels : [];
|
||||
const match = channels.find((channel) => channel?.id === channelName || channel?.name === channelName);
|
||||
if (!match?.id) {
|
||||
throw new Error(\`ClickClack channel not found: \${channelName}\`);
|
||||
}
|
||||
return match.id;
|
||||
}
|
||||
|
||||
async function sendText(cfg, to, text, accountId, threadId, replyToId) {
|
||||
const account = resolveAccount(cfg, accountId);
|
||||
if (!account.configured) {
|
||||
throw new Error("ClickClack is not configured");
|
||||
}
|
||||
const workspaceId = await resolveWorkspaceId(account);
|
||||
const rootId = threadId == null ? String(replyToId ?? "") : String(threadId);
|
||||
if (rootId) {
|
||||
const data = await requestJson(
|
||||
account,
|
||||
"POST",
|
||||
\`/api/messages/\${encodeURIComponent(rootId)}/thread/replies\`,
|
||||
{ body: text },
|
||||
);
|
||||
return data.message;
|
||||
}
|
||||
const channelId = await resolveChannelId(account, workspaceId, to);
|
||||
const data = await requestJson(account, "POST", \`/api/channels/\${encodeURIComponent(channelId)}/messages\`, {
|
||||
body: text,
|
||||
});
|
||||
return data.message;
|
||||
}
|
||||
|
||||
function decodeFrame(buffer) {
|
||||
if (buffer.length < 2) {
|
||||
return null;
|
||||
}
|
||||
const opcode = buffer[0] & 0x0f;
|
||||
let length = buffer[1] & 0x7f;
|
||||
let offset = 2;
|
||||
if (length === 126) {
|
||||
if (buffer.length < 4) {
|
||||
return null;
|
||||
}
|
||||
length = buffer.readUInt16BE(2);
|
||||
offset = 4;
|
||||
} else if (length === 127) {
|
||||
if (buffer.length < 10) {
|
||||
return null;
|
||||
}
|
||||
length = Number(buffer.readBigUInt64BE(2));
|
||||
offset = 10;
|
||||
}
|
||||
if (buffer.length < offset + length) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
opcode,
|
||||
text: buffer.subarray(offset, offset + length).toString("utf8"),
|
||||
rest: buffer.subarray(offset + length),
|
||||
};
|
||||
}
|
||||
|
||||
function openEventSocket(account, workspaceId, afterCursor, onEvent, signal) {
|
||||
const base = new URL(account.baseUrl);
|
||||
const key = crypto.randomBytes(16).toString("base64");
|
||||
const socket = net.createConnection({
|
||||
host: base.hostname,
|
||||
port: Number(base.port || (base.protocol === "https:" ? 443 : 80)),
|
||||
});
|
||||
let buffer = Buffer.alloc(0);
|
||||
let upgraded = false;
|
||||
const close = () => socket.destroy();
|
||||
signal.addEventListener("abort", close, { once: true });
|
||||
socket.on("connect", () => {
|
||||
const query = new URLSearchParams({ workspace_id: workspaceId });
|
||||
if (afterCursor) {
|
||||
query.set("after_cursor", afterCursor);
|
||||
}
|
||||
socket.write(
|
||||
[
|
||||
\`GET /api/realtime/ws?\${query.toString()} HTTP/1.1\`,
|
||||
\`Host: \${base.host}\`,
|
||||
"Upgrade: websocket",
|
||||
"Connection: Upgrade",
|
||||
\`Sec-WebSocket-Key: \${key}\`,
|
||||
"Sec-WebSocket-Version: 13",
|
||||
\`Authorization: Bearer \${account.token}\`,
|
||||
"",
|
||||
"",
|
||||
].join("\\r\\n"),
|
||||
);
|
||||
});
|
||||
socket.on("data", (chunk) => {
|
||||
buffer = Buffer.concat([buffer, chunk]);
|
||||
if (!upgraded) {
|
||||
const headerEnd = buffer.indexOf("\\r\\n\\r\\n");
|
||||
if (headerEnd === -1) {
|
||||
return;
|
||||
}
|
||||
const headers = buffer.subarray(0, headerEnd).toString("utf8");
|
||||
if (!headers.startsWith("HTTP/1.1 101")) {
|
||||
socket.destroy(new Error(headers.split("\\r\\n")[0] || "websocket upgrade failed"));
|
||||
return;
|
||||
}
|
||||
upgraded = true;
|
||||
buffer = buffer.subarray(headerEnd + 4);
|
||||
}
|
||||
for (;;) {
|
||||
const frame = decodeFrame(buffer);
|
||||
if (!frame) {
|
||||
return;
|
||||
}
|
||||
buffer = frame.rest;
|
||||
if (frame.opcode === 1) {
|
||||
onEvent(JSON.parse(frame.text));
|
||||
} else if (frame.opcode === 8) {
|
||||
socket.end();
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
socket.on("close", () => signal.removeEventListener("abort", close));
|
||||
return socket;
|
||||
}
|
||||
|
||||
async function resolveEventMessage(account, event) {
|
||||
if (event?.type !== "message.created" || !event.channel_id || typeof event.seq !== "number") {
|
||||
return null;
|
||||
}
|
||||
const data = await requestJson(
|
||||
account,
|
||||
"GET",
|
||||
\`/api/channels/\${encodeURIComponent(event.channel_id)}/messages?after_seq=\${Math.max(0, event.seq - 1)}\`,
|
||||
);
|
||||
const messages = Array.isArray(data.messages) ? data.messages : [];
|
||||
return messages.find((message) => message?.id === event.payload?.message_id) ?? null;
|
||||
}
|
||||
|
||||
async function dispatchInbound(ctx, account, message) {
|
||||
const runtime = ctx.channelRuntime;
|
||||
if (!runtime) {
|
||||
throw new Error("ClickClack fixture requires channel runtime");
|
||||
}
|
||||
const target = \`channel:\${message.channel_id}\`;
|
||||
const route = runtime.routing.resolveAgentRoute({
|
||||
cfg: ctx.cfg,
|
||||
channel: CHANNEL_ID,
|
||||
accountId: account.accountId,
|
||||
peer: { kind: "channel", id: target },
|
||||
});
|
||||
const storePath = runtime.session.resolveStorePath(ctx.cfg.session?.store, {
|
||||
agentId: route.agentId,
|
||||
});
|
||||
const previousTimestamp = runtime.session.readSessionUpdatedAt({
|
||||
storePath,
|
||||
sessionKey: route.sessionKey,
|
||||
});
|
||||
const senderName = message.author?.display_name || message.author_id || "Release User";
|
||||
const body = runtime.reply.formatAgentEnvelope({
|
||||
channel: "ClickClack",
|
||||
from: senderName,
|
||||
timestamp: new Date(message.created_at),
|
||||
previousTimestamp,
|
||||
envelope: runtime.reply.resolveEnvelopeFormatOptions(ctx.cfg),
|
||||
body: message.body,
|
||||
});
|
||||
const ctxPayload = runtime.reply.finalizeInboundContext({
|
||||
Body: body,
|
||||
BodyForAgent: message.body,
|
||||
RawBody: message.body,
|
||||
CommandBody: message.body,
|
||||
From: target,
|
||||
To: target,
|
||||
SessionKey: route.sessionKey,
|
||||
AccountId: route.accountId ?? account.accountId,
|
||||
ChatType: "group",
|
||||
WasMentioned: true,
|
||||
ConversationLabel: message.channel_id,
|
||||
GroupChannel: message.channel_id,
|
||||
NativeChannelId: message.channel_id,
|
||||
MessageSid: message.id,
|
||||
MessageSidFull: message.id,
|
||||
ReplyToId: message.id,
|
||||
Timestamp: message.created_at,
|
||||
OriginatingChannel: CHANNEL_ID,
|
||||
OriginatingTo: target,
|
||||
CommandAuthorized: true,
|
||||
});
|
||||
await runtime.reply.dispatchReplyWithBufferedBlockDispatcher({
|
||||
ctx: ctxPayload,
|
||||
cfg: ctx.cfg,
|
||||
dispatcherOptions: {
|
||||
deliver: async (payload) => {
|
||||
const text = payload && typeof payload === "object" ? String(payload.text ?? "") : "";
|
||||
if (text.trim()) {
|
||||
await sendText(ctx.cfg, target, text, account.accountId, message.id, message.id);
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
throw error instanceof Error ? error : new Error(String(error));
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const clickclackPlugin = {
|
||||
id: CHANNEL_ID,
|
||||
meta: {
|
||||
id: CHANNEL_ID,
|
||||
label: "ClickClack",
|
||||
selectionLabel: "ClickClack",
|
||||
docsPath: "/channels/clickclack",
|
||||
blurb: "Release journey ClickClack fixture.",
|
||||
},
|
||||
capabilities: { chatTypes: ["group"], threads: true },
|
||||
config: {
|
||||
listAccountIds: () => [DEFAULT_ACCOUNT_ID],
|
||||
defaultAccountId: () => DEFAULT_ACCOUNT_ID,
|
||||
resolveAccount,
|
||||
isConfigured: (account) => account.configured,
|
||||
isEnabled: (account) => account.enabled,
|
||||
resolveDefaultTo: ({ cfg }) => resolveAccount(cfg).defaultTo,
|
||||
},
|
||||
status: {
|
||||
buildChannelSummary: ({ snapshot }) => ({
|
||||
ok: snapshot.configured === true,
|
||||
label: snapshot.configured ? "configured" : "missing config",
|
||||
detail: snapshot.baseUrl ?? "",
|
||||
}),
|
||||
buildAccountSnapshot: ({ account }) => ({
|
||||
accountId: account.accountId,
|
||||
enabled: account.enabled,
|
||||
configured: account.configured,
|
||||
baseUrl: account.baseUrl,
|
||||
}),
|
||||
},
|
||||
outbound: {
|
||||
deliveryMode: "direct",
|
||||
sendText: async (ctx) => {
|
||||
const message = await sendText(ctx.cfg, ctx.to, ctx.text, ctx.accountId, ctx.threadId, ctx.replyToId);
|
||||
return { channel: CHANNEL_ID, messageId: message.id };
|
||||
},
|
||||
},
|
||||
gateway: {
|
||||
startAccount: async (ctx) => {
|
||||
const account = resolveAccount(ctx.cfg, ctx.account.accountId);
|
||||
if (!account.configured) {
|
||||
throw new Error("ClickClack is not configured");
|
||||
}
|
||||
const workspaceId = await resolveWorkspaceId(account);
|
||||
ctx.setStatus({
|
||||
accountId: account.accountId,
|
||||
running: true,
|
||||
configured: true,
|
||||
enabled: account.enabled,
|
||||
baseUrl: account.baseUrl,
|
||||
});
|
||||
try {
|
||||
while (!ctx.abortSignal.aborted) {
|
||||
const socket = openEventSocket(
|
||||
account,
|
||||
workspaceId,
|
||||
"",
|
||||
(event) => {
|
||||
void (async () => {
|
||||
const message = await resolveEventMessage(account, event);
|
||||
if (message && message.author?.kind !== "bot") {
|
||||
await dispatchInbound(ctx, account, message);
|
||||
}
|
||||
})().catch((error) => {
|
||||
ctx.log?.error?.(error instanceof Error ? error.message : String(error));
|
||||
});
|
||||
},
|
||||
ctx.abortSignal,
|
||||
);
|
||||
await new Promise((resolve) => {
|
||||
socket.once("close", resolve);
|
||||
socket.once("error", resolve);
|
||||
});
|
||||
if (!ctx.abortSignal.aborted) {
|
||||
await new Promise((resolve) => setTimeout(resolve, account.reconnectMs));
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
ctx.setStatus({ accountId: account.accountId, running: false });
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
id: CHANNEL_ID,
|
||||
register(api) {
|
||||
api.registerChannel({ plugin: clickclackPlugin });
|
||||
},
|
||||
};
|
||||
`,
|
||||
);
|
||||
Reference in New Issue
Block a user