mirror of
https://github.com/openclaw/openclaw.git
synced 2026-02-19 18:39:20 -05:00
TypeScript: add extensions to tsconfig and fix type errors (#12781)
* TypeScript: add extensions to tsconfig and fix type errors - Add extensions/**/* to tsconfig.json includes - Export ProviderAuthResult, AnyAgentTool from plugin-sdk - Fix optional chaining for messageActions across channels - Add missing type imports (MSTeamsConfig, GroupPolicy, etc.) - Add type annotations for provider auth handlers - Fix undici/fetch type compatibility in zalo proxy - Correct ChannelAccountSnapshot property usage - Add type casts for tool registrations - Extract usage view styles and types to separate files * TypeScript: fix optional debug calls and handleAction guards
This commit is contained in:
@@ -86,7 +86,7 @@ export const bluebubblesMessageActions: ChannelMessageActionAdapter = {
|
||||
if (!spec?.gate) {
|
||||
continue;
|
||||
}
|
||||
if (spec.unsupportedOnMacOS26 && macOS26) {
|
||||
if ("unsupportedOnMacOS26" in spec && spec.unsupportedOnMacOS26 && macOS26) {
|
||||
continue;
|
||||
}
|
||||
if (gate(spec.gate)) {
|
||||
|
||||
@@ -361,14 +361,16 @@ function combineDebounceEntries(entries: BlueBubblesDebounceEntry[]): Normalized
|
||||
|
||||
const webhookTargets = new Map<string, WebhookTarget[]>();
|
||||
|
||||
type BlueBubblesDebouncer = {
|
||||
enqueue: (item: BlueBubblesDebounceEntry) => Promise<void>;
|
||||
flushKey: (key: string) => Promise<void>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Maps webhook targets to their inbound debouncers.
|
||||
* Each target gets its own debouncer keyed by a unique identifier.
|
||||
*/
|
||||
const targetDebouncers = new Map<
|
||||
WebhookTarget,
|
||||
ReturnType<BlueBubblesCoreRuntime["channel"]["debounce"]["createInboundDebouncer"]>
|
||||
>();
|
||||
const targetDebouncers = new Map<WebhookTarget, BlueBubblesDebouncer>();
|
||||
|
||||
function resolveBlueBubblesDebounceMs(
|
||||
config: OpenClawConfig,
|
||||
@@ -1917,7 +1919,7 @@ async function processMessage(
|
||||
maxBytes,
|
||||
});
|
||||
const saved = await core.channel.media.saveMediaBuffer(
|
||||
downloaded.buffer,
|
||||
Buffer.from(downloaded.buffer),
|
||||
downloaded.contentType,
|
||||
"inbound",
|
||||
maxBytes,
|
||||
@@ -2349,7 +2351,7 @@ async function processMessage(
|
||||
},
|
||||
});
|
||||
}
|
||||
if (shouldStopTyping) {
|
||||
if (shouldStopTyping && chatGuidForActions) {
|
||||
// Stop typing after streaming completes to avoid a stuck indicator.
|
||||
sendBlueBubblesTyping(chatGuidForActions, false, {
|
||||
cfg: config,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export type { DmPolicy, GroupPolicy } from "openclaw/plugin-sdk";
|
||||
import type { DmPolicy, GroupPolicy } from "openclaw/plugin-sdk";
|
||||
export type { DmPolicy, GroupPolicy };
|
||||
|
||||
export type BlueBubblesGroupConfig = {
|
||||
/** If true, only respond in this group when mentioned. */
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
|
||||
import {
|
||||
emptyPluginConfigSchema,
|
||||
type OpenClawPluginApi,
|
||||
type ProviderAuthContext,
|
||||
type ProviderAuthResult,
|
||||
} from "openclaw/plugin-sdk";
|
||||
|
||||
const DEFAULT_BASE_URL = "http://localhost:3000/v1";
|
||||
const DEFAULT_API_KEY = "n/a";
|
||||
@@ -57,9 +62,9 @@ function buildModelDefinition(modelId: string) {
|
||||
return {
|
||||
id: modelId,
|
||||
name: modelId,
|
||||
api: "openai-completions",
|
||||
api: "openai-completions" as const,
|
||||
reasoning: false,
|
||||
input: ["text", "image"],
|
||||
input: ["text", "image"] as Array<"text" | "image">,
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: DEFAULT_CONTEXT_WINDOW,
|
||||
maxTokens: DEFAULT_MAX_TOKENS,
|
||||
@@ -71,7 +76,7 @@ const copilotProxyPlugin = {
|
||||
name: "Copilot Proxy",
|
||||
description: "Local Copilot Proxy (VS Code LM) provider plugin",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api) {
|
||||
register(api: OpenClawPluginApi) {
|
||||
api.registerProvider({
|
||||
id: "copilot-proxy",
|
||||
label: "Copilot Proxy",
|
||||
@@ -82,7 +87,7 @@ const copilotProxyPlugin = {
|
||||
label: "Local proxy",
|
||||
hint: "Configure base URL + models for the Copilot Proxy server",
|
||||
kind: "custom",
|
||||
run: async (ctx) => {
|
||||
run: async (ctx: ProviderAuthContext): Promise<ProviderAuthResult> => {
|
||||
const baseUrlInput = await ctx.prompter.text({
|
||||
message: "Copilot Proxy base URL",
|
||||
initialValue: DEFAULT_BASE_URL,
|
||||
@@ -92,7 +97,7 @@ const copilotProxyPlugin = {
|
||||
const modelInput = await ctx.prompter.text({
|
||||
message: "Model IDs (comma-separated)",
|
||||
initialValue: DEFAULT_MODEL_IDS.join(", "),
|
||||
validate: (value) =>
|
||||
validate: (value: string) =>
|
||||
parseModelIds(value).length > 0 ? undefined : "Enter at least one model id",
|
||||
});
|
||||
|
||||
|
||||
@@ -128,7 +128,8 @@ function pickLanIPv4(): string | null {
|
||||
}
|
||||
for (const entry of entries) {
|
||||
const family = entry?.family;
|
||||
const isIpv4 = family === "IPv4" || family === 4;
|
||||
// Check for IPv4 (string "IPv4" on Node 18+, number 4 on older)
|
||||
const isIpv4 = family === "IPv4" || String(family) === "4";
|
||||
if (!entry || entry.internal || !isIpv4) {
|
||||
continue;
|
||||
}
|
||||
@@ -152,7 +153,8 @@ function pickTailnetIPv4(): string | null {
|
||||
}
|
||||
for (const entry of entries) {
|
||||
const family = entry?.family;
|
||||
const isIpv4 = family === "IPv4" || family === 4;
|
||||
// Check for IPv4 (string "IPv4" on Node 18+, number 4 on older)
|
||||
const isIpv4 = family === "IPv4" || String(family) === "4";
|
||||
if (!entry || entry.internal || !isIpv4) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { metrics, trace, SpanStatusCode } from "@opentelemetry/api";
|
||||
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
|
||||
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
|
||||
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
||||
import { Resource } from "@opentelemetry/resources";
|
||||
import { resourceFromAttributes } from "@opentelemetry/resources";
|
||||
import { BatchLogRecordProcessor, LoggerProvider } from "@opentelemetry/sdk-logs";
|
||||
import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
|
||||
import { NodeSDK } from "@opentelemetry/sdk-node";
|
||||
@@ -73,7 +73,7 @@ export function createDiagnosticsOtelService(): OpenClawPluginService {
|
||||
return;
|
||||
}
|
||||
|
||||
const resource = new Resource({
|
||||
const resource = resourceFromAttributes({
|
||||
[SemanticResourceAttributes.SERVICE_NAME]: serviceName,
|
||||
});
|
||||
|
||||
@@ -210,15 +210,13 @@ export function createDiagnosticsOtelService(): OpenClawPluginService {
|
||||
...(logUrl ? { url: logUrl } : {}),
|
||||
...(headers ? { headers } : {}),
|
||||
});
|
||||
logProvider = new LoggerProvider({ resource });
|
||||
logProvider.addLogRecordProcessor(
|
||||
new BatchLogRecordProcessor(
|
||||
logExporter,
|
||||
typeof otel.flushIntervalMs === "number"
|
||||
? { scheduledDelayMillis: Math.max(1000, otel.flushIntervalMs) }
|
||||
: {},
|
||||
),
|
||||
const processor = new BatchLogRecordProcessor(
|
||||
logExporter,
|
||||
typeof otel.flushIntervalMs === "number"
|
||||
? { scheduledDelayMillis: Math.max(1000, otel.flushIntervalMs) }
|
||||
: {},
|
||||
);
|
||||
logProvider = new LoggerProvider({ resource, processors: [processor] });
|
||||
const otelLogger = logProvider.getLogger("openclaw");
|
||||
|
||||
stopLogTransport = registerLogTransport((logObj) => {
|
||||
|
||||
@@ -31,10 +31,17 @@ import { getDiscordRuntime } from "./runtime.js";
|
||||
const meta = getChatChannelMeta("discord");
|
||||
|
||||
const discordMessageActions: ChannelMessageActionAdapter = {
|
||||
listActions: (ctx) => getDiscordRuntime().channel.discord.messageActions.listActions(ctx),
|
||||
extractToolSend: (ctx) => getDiscordRuntime().channel.discord.messageActions.extractToolSend(ctx),
|
||||
handleAction: async (ctx) =>
|
||||
await getDiscordRuntime().channel.discord.messageActions.handleAction(ctx),
|
||||
listActions: (ctx) =>
|
||||
getDiscordRuntime().channel.discord.messageActions?.listActions?.(ctx) ?? [],
|
||||
extractToolSend: (ctx) =>
|
||||
getDiscordRuntime().channel.discord.messageActions?.extractToolSend?.(ctx) ?? null,
|
||||
handleAction: async (ctx) => {
|
||||
const ma = getDiscordRuntime().channel.discord.messageActions;
|
||||
if (!ma?.handleAction) {
|
||||
throw new Error("Discord message actions not available");
|
||||
}
|
||||
return ma.handleAction(ctx);
|
||||
},
|
||||
};
|
||||
|
||||
export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
|
||||
|
||||
@@ -212,7 +212,8 @@ async function createRecord(
|
||||
) {
|
||||
const res = await client.bitable.appTableRecord.create({
|
||||
path: { app_token: appToken, table_id: tableId },
|
||||
data: { fields },
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
data: { fields: fields as any },
|
||||
});
|
||||
if (res.code !== 0) {
|
||||
throw new Error(res.msg);
|
||||
@@ -232,7 +233,8 @@ async function updateRecord(
|
||||
) {
|
||||
const res = await client.bitable.appTableRecord.update({
|
||||
path: { app_token: appToken, table_id: tableId, record_id: recordId },
|
||||
data: { fields },
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
data: { fields: fields as any },
|
||||
});
|
||||
if (res.code !== 0) {
|
||||
throw new Error(res.msg);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ChannelPlugin, ClawdbotConfig } from "openclaw/plugin-sdk";
|
||||
import type { ChannelMeta, ChannelPlugin, ClawdbotConfig } from "openclaw/plugin-sdk";
|
||||
import { DEFAULT_ACCOUNT_ID, PAIRING_APPROVED_MESSAGE } from "openclaw/plugin-sdk";
|
||||
import type { ResolvedFeishuAccount, FeishuConfig } from "./types.js";
|
||||
import {
|
||||
@@ -19,7 +19,7 @@ import { probeFeishu } from "./probe.js";
|
||||
import { sendMessageFeishu } from "./send.js";
|
||||
import { normalizeFeishuTarget, looksLikeFeishuId } from "./targets.js";
|
||||
|
||||
const meta = {
|
||||
const meta: ChannelMeta = {
|
||||
id: "feishu",
|
||||
label: "Feishu",
|
||||
selectionLabel: "Feishu/Lark (飞书)",
|
||||
@@ -28,7 +28,7 @@ const meta = {
|
||||
blurb: "飞书/Lark enterprise messaging.",
|
||||
aliases: ["lark"],
|
||||
order: 70,
|
||||
} as const;
|
||||
};
|
||||
|
||||
export const feishuPlugin: ChannelPlugin<ResolvedFeishuAccount> = {
|
||||
id: "feishu",
|
||||
@@ -38,12 +38,11 @@ export const feishuPlugin: ChannelPlugin<ResolvedFeishuAccount> = {
|
||||
pairing: {
|
||||
idLabel: "feishuUserId",
|
||||
normalizeAllowEntry: (entry) => entry.replace(/^(feishu|user|open_id):/i, ""),
|
||||
notifyApproval: async ({ cfg, id, accountId }) => {
|
||||
notifyApproval: async ({ cfg, id }) => {
|
||||
await sendMessageFeishu({
|
||||
cfg,
|
||||
to: id,
|
||||
text: PAIRING_APPROVED_MESSAGE,
|
||||
accountId,
|
||||
});
|
||||
},
|
||||
},
|
||||
@@ -202,7 +201,7 @@ export const feishuPlugin: ChannelPlugin<ResolvedFeishuAccount> = {
|
||||
}),
|
||||
resolveAllowFrom: ({ cfg, accountId }) => {
|
||||
const account = resolveFeishuAccount({ cfg, accountId });
|
||||
return account.config?.allowFrom ?? [];
|
||||
return (account.config?.allowFrom ?? []).map((entry) => String(entry));
|
||||
},
|
||||
formatAllowFrom: ({ allowFrom }) =>
|
||||
allowFrom
|
||||
@@ -265,7 +264,7 @@ export const feishuPlugin: ChannelPlugin<ResolvedFeishuAccount> = {
|
||||
},
|
||||
onboarding: feishuOnboardingAdapter,
|
||||
messaging: {
|
||||
normalizeTarget: normalizeFeishuTarget,
|
||||
normalizeTarget: (raw) => normalizeFeishuTarget(raw) ?? undefined,
|
||||
targetResolver: {
|
||||
looksLikeId: looksLikeFeishuId,
|
||||
hint: "<chatId|user:openId|chat:chatId>",
|
||||
@@ -274,13 +273,33 @@ export const feishuPlugin: ChannelPlugin<ResolvedFeishuAccount> = {
|
||||
directory: {
|
||||
self: async () => null,
|
||||
listPeers: async ({ cfg, query, limit, accountId }) =>
|
||||
listFeishuDirectoryPeers({ cfg, query, limit, accountId }),
|
||||
listFeishuDirectoryPeers({
|
||||
cfg,
|
||||
query: query ?? undefined,
|
||||
limit: limit ?? undefined,
|
||||
accountId: accountId ?? undefined,
|
||||
}),
|
||||
listGroups: async ({ cfg, query, limit, accountId }) =>
|
||||
listFeishuDirectoryGroups({ cfg, query, limit, accountId }),
|
||||
listFeishuDirectoryGroups({
|
||||
cfg,
|
||||
query: query ?? undefined,
|
||||
limit: limit ?? undefined,
|
||||
accountId: accountId ?? undefined,
|
||||
}),
|
||||
listPeersLive: async ({ cfg, query, limit, accountId }) =>
|
||||
listFeishuDirectoryPeersLive({ cfg, query, limit, accountId }),
|
||||
listFeishuDirectoryPeersLive({
|
||||
cfg,
|
||||
query: query ?? undefined,
|
||||
limit: limit ?? undefined,
|
||||
accountId: accountId ?? undefined,
|
||||
}),
|
||||
listGroupsLive: async ({ cfg, query, limit, accountId }) =>
|
||||
listFeishuDirectoryGroupsLive({ cfg, query, limit, accountId }),
|
||||
listFeishuDirectoryGroupsLive({
|
||||
cfg,
|
||||
query: query ?? undefined,
|
||||
limit: limit ?? undefined,
|
||||
accountId: accountId ?? undefined,
|
||||
}),
|
||||
},
|
||||
outbound: feishuOutbound,
|
||||
status: {
|
||||
@@ -302,8 +321,7 @@ export const feishuPlugin: ChannelPlugin<ResolvedFeishuAccount> = {
|
||||
probe: snapshot.probe,
|
||||
lastProbeAt: snapshot.lastProbeAt ?? null,
|
||||
}),
|
||||
probeAccount: async ({ cfg, accountId }) => {
|
||||
const account = resolveFeishuAccount({ cfg, accountId });
|
||||
probeAccount: async ({ account }) => {
|
||||
return await probeFeishu(account);
|
||||
},
|
||||
buildAccountSnapshot: ({ account, runtime, probe }) => ({
|
||||
|
||||
@@ -80,7 +80,10 @@ async function promptFeishuAllowFrom(params: {
|
||||
}
|
||||
|
||||
const unique = [
|
||||
...new Set([...existing.map((v) => String(v).trim()).filter(Boolean), ...parts]),
|
||||
...new Set([
|
||||
...existing.map((v: string | number) => String(v).trim()).filter(Boolean),
|
||||
...parts,
|
||||
]),
|
||||
];
|
||||
return setFeishuAllowFrom(params.cfg, unique);
|
||||
}
|
||||
|
||||
@@ -9,32 +9,47 @@ export const feishuOutbound: ChannelOutboundAdapter = {
|
||||
chunkerMode: "markdown",
|
||||
textChunkLimit: 4000,
|
||||
sendText: async ({ cfg, to, text, accountId }) => {
|
||||
const result = await sendMessageFeishu({ cfg, to, text, accountId });
|
||||
const result = await sendMessageFeishu({ cfg, to, text, accountId: accountId ?? undefined });
|
||||
return { channel: "feishu", ...result };
|
||||
},
|
||||
sendMedia: async ({ cfg, to, text, mediaUrl, accountId }) => {
|
||||
// Send text first if provided
|
||||
if (text?.trim()) {
|
||||
await sendMessageFeishu({ cfg, to, text, accountId });
|
||||
await sendMessageFeishu({ cfg, to, text, accountId: accountId ?? undefined });
|
||||
}
|
||||
|
||||
// Upload and send media if URL provided
|
||||
if (mediaUrl) {
|
||||
try {
|
||||
const result = await sendMediaFeishu({ cfg, to, mediaUrl, accountId });
|
||||
const result = await sendMediaFeishu({
|
||||
cfg,
|
||||
to,
|
||||
mediaUrl,
|
||||
accountId: accountId ?? undefined,
|
||||
});
|
||||
return { channel: "feishu", ...result };
|
||||
} catch (err) {
|
||||
// Log the error for debugging
|
||||
console.error(`[feishu] sendMediaFeishu failed:`, err);
|
||||
// Fallback to URL link if upload fails
|
||||
const fallbackText = `📎 ${mediaUrl}`;
|
||||
const result = await sendMessageFeishu({ cfg, to, text: fallbackText, accountId });
|
||||
const result = await sendMessageFeishu({
|
||||
cfg,
|
||||
to,
|
||||
text: fallbackText,
|
||||
accountId: accountId ?? undefined,
|
||||
});
|
||||
return { channel: "feishu", ...result };
|
||||
}
|
||||
}
|
||||
|
||||
// No media URL, just return text result
|
||||
const result = await sendMessageFeishu({ cfg, to, text: text ?? "", accountId });
|
||||
const result = await sendMessageFeishu({
|
||||
cfg,
|
||||
to,
|
||||
text: text ?? "",
|
||||
accountId: accountId ?? undefined,
|
||||
});
|
||||
return { channel: "feishu", ...result };
|
||||
},
|
||||
};
|
||||
|
||||
@@ -90,16 +90,11 @@ export function createFeishuReplyDispatcher(params: CreateFeishuReplyDispatcherP
|
||||
},
|
||||
});
|
||||
|
||||
const textChunkLimit = core.channel.text.resolveTextChunkLimit({
|
||||
cfg,
|
||||
channel: "feishu",
|
||||
defaultLimit: 4000,
|
||||
const textChunkLimit = core.channel.text.resolveTextChunkLimit(cfg, "feishu", accountId, {
|
||||
fallbackLimit: 4000,
|
||||
});
|
||||
const chunkMode = core.channel.text.resolveChunkMode(cfg, "feishu");
|
||||
const tableMode = core.channel.text.resolveMarkdownTableMode({
|
||||
cfg,
|
||||
channel: "feishu",
|
||||
});
|
||||
const tableMode = core.channel.text.resolveMarkdownTableMode({ cfg, channel: "feishu" });
|
||||
|
||||
const { dispatcher, replyOptions, markDispatchIdle } =
|
||||
core.channel.reply.createReplyDispatcherWithTyping({
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { createHash, randomBytes } from "node:crypto";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { createServer } from "node:http";
|
||||
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
|
||||
import {
|
||||
emptyPluginConfigSchema,
|
||||
type OpenClawPluginApi,
|
||||
type ProviderAuthContext,
|
||||
} from "openclaw/plugin-sdk";
|
||||
|
||||
// OAuth constants - decoded from pi-ai's base64 encoded values to stay in sync
|
||||
const decode = (s: string) => Buffer.from(s, "base64").toString();
|
||||
@@ -392,7 +396,7 @@ const antigravityPlugin = {
|
||||
name: "Google Antigravity Auth",
|
||||
description: "OAuth flow for Google Antigravity (Cloud Code Assist)",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api) {
|
||||
register(api: OpenClawPluginApi) {
|
||||
api.registerProvider({
|
||||
id: "google-antigravity",
|
||||
label: "Google Antigravity",
|
||||
@@ -404,7 +408,7 @@ const antigravityPlugin = {
|
||||
label: "Google OAuth",
|
||||
hint: "PKCE + localhost callback",
|
||||
kind: "oauth",
|
||||
run: async (ctx) => {
|
||||
run: async (ctx: ProviderAuthContext) => {
|
||||
const spin = ctx.prompter.progress("Starting Antigravity OAuth…");
|
||||
try {
|
||||
const result = await loginAntigravity({
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
|
||||
import {
|
||||
emptyPluginConfigSchema,
|
||||
type OpenClawPluginApi,
|
||||
type ProviderAuthContext,
|
||||
} from "openclaw/plugin-sdk";
|
||||
import { loginGeminiCliOAuth } from "./oauth.js";
|
||||
|
||||
const PROVIDER_ID = "google-gemini-cli";
|
||||
@@ -16,7 +20,7 @@ const geminiCliPlugin = {
|
||||
name: "Google Gemini CLI Auth",
|
||||
description: "OAuth flow for Gemini CLI (Google Code Assist)",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api) {
|
||||
register(api: OpenClawPluginApi) {
|
||||
api.registerProvider({
|
||||
id: PROVIDER_ID,
|
||||
label: PROVIDER_LABEL,
|
||||
@@ -29,7 +33,7 @@ const geminiCliPlugin = {
|
||||
label: "Google OAuth",
|
||||
hint: "PKCE + localhost callback",
|
||||
kind: "oauth",
|
||||
run: async (ctx) => {
|
||||
run: async (ctx: ProviderAuthContext) => {
|
||||
const spin = ctx.prompter.progress("Starting Gemini CLI OAuth…");
|
||||
try {
|
||||
const result = await loginGeminiCliOAuth({
|
||||
|
||||
@@ -97,11 +97,11 @@ export const googlechatMessageActions: ChannelMessageActionAdapter = {
|
||||
if (mediaUrl) {
|
||||
const core = getGoogleChatRuntime();
|
||||
const maxBytes = (account.config.mediaMaxMb ?? 20) * 1024 * 1024;
|
||||
const loaded = await core.channel.media.fetchRemoteMedia(mediaUrl, { maxBytes });
|
||||
const loaded = await core.channel.media.fetchRemoteMedia({ url: mediaUrl, maxBytes });
|
||||
const upload = await uploadGoogleChatAttachment({
|
||||
account,
|
||||
space,
|
||||
filename: loaded.filename ?? "attachment",
|
||||
filename: loaded.fileName ?? "attachment",
|
||||
buffer: loaded.buffer,
|
||||
contentType: loaded.contentType,
|
||||
});
|
||||
@@ -114,7 +114,7 @@ export const googlechatMessageActions: ChannelMessageActionAdapter = {
|
||||
? [
|
||||
{
|
||||
attachmentUploadToken: upload.attachmentUploadToken,
|
||||
contentName: loaded.filename,
|
||||
contentName: loaded.fileName,
|
||||
},
|
||||
]
|
||||
: undefined,
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
type ChannelDock,
|
||||
type ChannelMessageActionAdapter,
|
||||
type ChannelPlugin,
|
||||
type ChannelStatusIssue,
|
||||
type OpenClawConfig,
|
||||
} from "openclaw/plugin-sdk";
|
||||
import { GoogleChatConfigSchema } from "openclaw/plugin-sdk";
|
||||
@@ -451,13 +452,14 @@ export const googlechatPlugin: ChannelPlugin<ResolvedGoogleChatAccount> = {
|
||||
(cfg.channels?.["googlechat"] as { mediaMaxMb?: number } | undefined)?.mediaMaxMb,
|
||||
accountId,
|
||||
});
|
||||
const loaded = await runtime.channel.media.fetchRemoteMedia(mediaUrl, {
|
||||
const loaded = await runtime.channel.media.fetchRemoteMedia({
|
||||
url: mediaUrl,
|
||||
maxBytes: maxBytes ?? (account.config.mediaMaxMb ?? 20) * 1024 * 1024,
|
||||
});
|
||||
const upload = await uploadGoogleChatAttachment({
|
||||
account,
|
||||
space,
|
||||
filename: loaded.filename ?? "attachment",
|
||||
filename: loaded.fileName ?? "attachment",
|
||||
buffer: loaded.buffer,
|
||||
contentType: loaded.contentType,
|
||||
});
|
||||
@@ -467,7 +469,7 @@ export const googlechatPlugin: ChannelPlugin<ResolvedGoogleChatAccount> = {
|
||||
text,
|
||||
thread,
|
||||
attachments: upload.attachmentUploadToken
|
||||
? [{ attachmentUploadToken: upload.attachmentUploadToken, contentName: loaded.filename }]
|
||||
? [{ attachmentUploadToken: upload.attachmentUploadToken, contentName: loaded.fileName }]
|
||||
: undefined,
|
||||
});
|
||||
return {
|
||||
@@ -485,7 +487,7 @@ export const googlechatPlugin: ChannelPlugin<ResolvedGoogleChatAccount> = {
|
||||
lastStopAt: null,
|
||||
lastError: null,
|
||||
},
|
||||
collectStatusIssues: (accounts) =>
|
||||
collectStatusIssues: (accounts): ChannelStatusIssue[] =>
|
||||
accounts.flatMap((entry) => {
|
||||
const accountId = String(entry.accountId ?? DEFAULT_ACCOUNT_ID);
|
||||
const enabled = entry.enabled !== false;
|
||||
@@ -493,7 +495,7 @@ export const googlechatPlugin: ChannelPlugin<ResolvedGoogleChatAccount> = {
|
||||
if (!enabled || !configured) {
|
||||
return [];
|
||||
}
|
||||
const issues = [];
|
||||
const issues: ChannelStatusIssue[] = [];
|
||||
if (!entry.audience) {
|
||||
issues.push({
|
||||
channel: "googlechat",
|
||||
|
||||
@@ -835,7 +835,8 @@ async function deliverGoogleChatReply(params: {
|
||||
const caption = first && !suppressCaption ? payload.text : undefined;
|
||||
first = false;
|
||||
try {
|
||||
const loaded = await core.channel.media.fetchRemoteMedia(mediaUrl, {
|
||||
const loaded = await core.channel.media.fetchRemoteMedia({
|
||||
url: mediaUrl,
|
||||
maxBytes: (account.config.mediaMaxMb ?? 20) * 1024 * 1024,
|
||||
});
|
||||
const upload = await uploadAttachmentForReply({
|
||||
@@ -843,7 +844,7 @@ async function deliverGoogleChatReply(params: {
|
||||
spaceId,
|
||||
buffer: loaded.buffer,
|
||||
contentType: loaded.contentType,
|
||||
filename: loaded.filename ?? "attachment",
|
||||
filename: loaded.fileName ?? "attachment",
|
||||
});
|
||||
if (!upload.attachmentUploadToken) {
|
||||
throw new Error("missing attachment upload token");
|
||||
@@ -854,7 +855,7 @@ async function deliverGoogleChatReply(params: {
|
||||
text: caption,
|
||||
thread: payload.replyToId,
|
||||
attachments: [
|
||||
{ attachmentUploadToken: upload.attachmentUploadToken, contentName: loaded.filename },
|
||||
{ attachmentUploadToken: upload.attachmentUploadToken, contentName: loaded.fileName },
|
||||
],
|
||||
});
|
||||
statusSink?.({ lastOutboundAt: Date.now() });
|
||||
|
||||
@@ -60,7 +60,7 @@ export const linePlugin: ChannelPlugin<ResolvedLineAccount> = {
|
||||
config: {
|
||||
listAccountIds: (cfg) => getLineRuntime().channel.line.listLineAccountIds(cfg),
|
||||
resolveAccount: (cfg, accountId) =>
|
||||
getLineRuntime().channel.line.resolveLineAccount({ cfg, accountId }),
|
||||
getLineRuntime().channel.line.resolveLineAccount({ cfg, accountId: accountId ?? undefined }),
|
||||
defaultAccountId: (cfg) => getLineRuntime().channel.line.resolveDefaultLineAccountId(cfg),
|
||||
setAccountEnabled: ({ cfg, accountId, enabled }) => {
|
||||
const lineConfig = (cfg.channels?.line ?? {}) as LineConfig;
|
||||
@@ -125,11 +125,12 @@ export const linePlugin: ChannelPlugin<ResolvedLineAccount> = {
|
||||
name: account.name,
|
||||
enabled: account.enabled,
|
||||
configured: Boolean(account.channelAccessToken?.trim()),
|
||||
tokenSource: account.tokenSource,
|
||||
tokenSource: account.tokenSource ?? undefined,
|
||||
}),
|
||||
resolveAllowFrom: ({ cfg, accountId }) =>
|
||||
(
|
||||
getLineRuntime().channel.line.resolveLineAccount({ cfg, accountId }).config.allowFrom ?? []
|
||||
getLineRuntime().channel.line.resolveLineAccount({ cfg, accountId: accountId ?? undefined })
|
||||
.config.allowFrom ?? []
|
||||
).map((entry) => String(entry)),
|
||||
formatAllowFrom: ({ allowFrom }) =>
|
||||
allowFrom
|
||||
@@ -172,9 +173,12 @@ export const linePlugin: ChannelPlugin<ResolvedLineAccount> = {
|
||||
},
|
||||
groups: {
|
||||
resolveRequireMention: ({ cfg, accountId, groupId }) => {
|
||||
const account = getLineRuntime().channel.line.resolveLineAccount({ cfg, accountId });
|
||||
const account = getLineRuntime().channel.line.resolveLineAccount({
|
||||
cfg,
|
||||
accountId: accountId ?? undefined,
|
||||
});
|
||||
const groups = account.config.groups;
|
||||
if (!groups) {
|
||||
if (!groups || !groupId) {
|
||||
return false;
|
||||
}
|
||||
const groupConfig = groups[groupId] ?? groups["*"];
|
||||
@@ -185,7 +189,7 @@ export const linePlugin: ChannelPlugin<ResolvedLineAccount> = {
|
||||
normalizeTarget: (target) => {
|
||||
const trimmed = target.trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
return undefined;
|
||||
}
|
||||
return trimmed.replace(/^line:(group|room|user):/i, "").replace(/^line:/i, "");
|
||||
},
|
||||
@@ -351,12 +355,15 @@ export const linePlugin: ChannelPlugin<ResolvedLineAccount> = {
|
||||
const hasQuickReplies = quickReplies.length > 0;
|
||||
const quickReply = hasQuickReplies ? createQuickReplyItems(quickReplies) : undefined;
|
||||
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
const sendMessageBatch = async (messages: Array<Record<string, unknown>>) => {
|
||||
if (messages.length === 0) {
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < messages.length; i += 5) {
|
||||
const result = await sendBatch(to, messages.slice(i, i + 5), {
|
||||
// LINE SDK expects Message[] but we build dynamically
|
||||
const batch = messages.slice(i, i + 5) as unknown as Parameters<typeof sendBatch>[1];
|
||||
const result = await sendBatch(to, batch, {
|
||||
verbose: false,
|
||||
accountId: accountId ?? undefined,
|
||||
});
|
||||
@@ -381,15 +388,12 @@ export const linePlugin: ChannelPlugin<ResolvedLineAccount> = {
|
||||
|
||||
if (!shouldSendQuickRepliesInline) {
|
||||
if (lineData.flexMessage) {
|
||||
lastResult = await sendFlex(
|
||||
to,
|
||||
lineData.flexMessage.altText,
|
||||
lineData.flexMessage.contents,
|
||||
{
|
||||
verbose: false,
|
||||
accountId: accountId ?? undefined,
|
||||
},
|
||||
);
|
||||
// LINE SDK expects FlexContainer but we receive contents as unknown
|
||||
const flexContents = lineData.flexMessage.contents as Parameters<typeof sendFlex>[2];
|
||||
lastResult = await sendFlex(to, lineData.flexMessage.altText, flexContents, {
|
||||
verbose: false,
|
||||
accountId: accountId ?? undefined,
|
||||
});
|
||||
}
|
||||
|
||||
if (lineData.templateMessage) {
|
||||
@@ -410,7 +414,9 @@ export const linePlugin: ChannelPlugin<ResolvedLineAccount> = {
|
||||
}
|
||||
|
||||
for (const flexMsg of processed.flexMessages) {
|
||||
lastResult = await sendFlex(to, flexMsg.altText, flexMsg.contents, {
|
||||
// LINE SDK expects FlexContainer but we receive contents as unknown
|
||||
const flexContents = flexMsg.contents as Parameters<typeof sendFlex>[2];
|
||||
lastResult = await sendFlex(to, flexMsg.altText, flexContents, {
|
||||
verbose: false,
|
||||
accountId: accountId ?? undefined,
|
||||
});
|
||||
@@ -532,7 +538,9 @@ export const linePlugin: ChannelPlugin<ResolvedLineAccount> = {
|
||||
|
||||
// Send flex messages for tables/code blocks
|
||||
for (const flexMsg of processed.flexMessages) {
|
||||
await sendFlex(to, flexMsg.altText, flexMsg.contents, {
|
||||
// LINE SDK expects FlexContainer but we receive contents as unknown
|
||||
const flexContents = flexMsg.contents as Parameters<typeof sendFlex>[2];
|
||||
await sendFlex(to, flexMsg.altText, flexContents, {
|
||||
verbose: false,
|
||||
accountId: accountId ?? undefined,
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { OpenClawPluginApi } from "../../src/plugins/types.js";
|
||||
import type { AnyAgentTool, OpenClawPluginApi } from "../../src/plugins/types.js";
|
||||
import { createLlmTaskTool } from "./src/llm-task-tool.js";
|
||||
|
||||
export default function register(api: OpenClawPluginApi) {
|
||||
api.registerTool(createLlmTaskTool(api), { optional: true });
|
||||
api.registerTool(createLlmTaskTool(api) as unknown as AnyAgentTool, { optional: true });
|
||||
}
|
||||
|
||||
@@ -25,11 +25,11 @@ async function loadRunEmbeddedPiAgent(): Promise<RunEmbeddedPiAgentFn> {
|
||||
}
|
||||
|
||||
// Bundled install (built)
|
||||
const mod = await import("../../../agents/pi-embedded-runner.js");
|
||||
const mod = await import("../../../src/agents/pi-embedded-runner.js");
|
||||
if (typeof mod.runEmbeddedPiAgent !== "function") {
|
||||
throw new Error("Internal error: runEmbeddedPiAgent not available");
|
||||
}
|
||||
return mod.runEmbeddedPiAgent;
|
||||
return mod.runEmbeddedPiAgent as RunEmbeddedPiAgentFn;
|
||||
}
|
||||
|
||||
function stripCodeFences(s: string): string {
|
||||
@@ -69,6 +69,7 @@ type PluginCfg = {
|
||||
export function createLlmTaskTool(api: OpenClawPluginApi) {
|
||||
return {
|
||||
name: "llm-task",
|
||||
label: "LLM Task",
|
||||
description:
|
||||
"Run a generic JSON-only LLM task and return schema-validated JSON. Designed for orchestration from Lobster workflows via openclaw.invoke.",
|
||||
parameters: Type.Object({
|
||||
@@ -214,14 +215,17 @@ export function createLlmTaskTool(api: OpenClawPluginApi) {
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
const schema = (params as any).schema as unknown;
|
||||
if (schema && typeof schema === "object" && !Array.isArray(schema)) {
|
||||
const ajv = new Ajv({ allErrors: true, strict: false });
|
||||
const ajv = new Ajv.default({ allErrors: true, strict: false });
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
const validate = ajv.compile(schema as any);
|
||||
const ok = validate(parsed);
|
||||
if (!ok) {
|
||||
const msg =
|
||||
validate.errors
|
||||
?.map((e) => `${e.instancePath || "<root>"} ${e.message || "invalid"}`)
|
||||
?.map(
|
||||
(e: { instancePath?: string; message?: string }) =>
|
||||
`${e.instancePath || "<root>"} ${e.message || "invalid"}`,
|
||||
)
|
||||
.join("; ") ?? "invalid";
|
||||
throw new Error(`LLM JSON did not match schema: ${msg}`);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import type { OpenClawPluginApi } from "../../src/plugins/types.js";
|
||||
import type {
|
||||
AnyAgentTool,
|
||||
OpenClawPluginApi,
|
||||
OpenClawPluginToolFactory,
|
||||
} from "../../src/plugins/types.js";
|
||||
import { createLobsterTool } from "./src/lobster-tool.js";
|
||||
|
||||
export default function register(api: OpenClawPluginApi) {
|
||||
api.registerTool(
|
||||
(ctx) => {
|
||||
((ctx) => {
|
||||
if (ctx.sandboxed) {
|
||||
return null;
|
||||
}
|
||||
return createLobsterTool(api);
|
||||
},
|
||||
return createLobsterTool(api) as AnyAgentTool;
|
||||
}) as OpenClawPluginToolFactory,
|
||||
{ optional: true },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -232,6 +232,7 @@ function parseEnvelope(stdout: string): LobsterEnvelope {
|
||||
export function createLobsterTool(api: OpenClawPluginApi) {
|
||||
return {
|
||||
name: "lobster",
|
||||
label: "Lobster Workflow",
|
||||
description:
|
||||
"Run Lobster pipelines as a local-first workflow runtime (typed JSON envelope + resumable approvals).",
|
||||
parameters: Type.Object({
|
||||
|
||||
@@ -78,7 +78,7 @@ export const matrixMessageActions: ChannelMessageActionAdapter = {
|
||||
replyToId: replyTo ?? undefined,
|
||||
threadId: threadId ?? undefined,
|
||||
},
|
||||
cfg,
|
||||
cfg as CoreConfig,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ export const matrixMessageActions: ChannelMessageActionAdapter = {
|
||||
emoji,
|
||||
remove,
|
||||
},
|
||||
cfg,
|
||||
cfg as CoreConfig,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ export const matrixMessageActions: ChannelMessageActionAdapter = {
|
||||
messageId,
|
||||
limit,
|
||||
},
|
||||
cfg,
|
||||
cfg as CoreConfig,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ export const matrixMessageActions: ChannelMessageActionAdapter = {
|
||||
before: readStringParam(params, "before"),
|
||||
after: readStringParam(params, "after"),
|
||||
},
|
||||
cfg,
|
||||
cfg as CoreConfig,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ export const matrixMessageActions: ChannelMessageActionAdapter = {
|
||||
messageId,
|
||||
content,
|
||||
},
|
||||
cfg,
|
||||
cfg as CoreConfig,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ export const matrixMessageActions: ChannelMessageActionAdapter = {
|
||||
roomId: resolveRoomId(),
|
||||
messageId,
|
||||
},
|
||||
cfg,
|
||||
cfg as CoreConfig,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ export const matrixMessageActions: ChannelMessageActionAdapter = {
|
||||
roomId: resolveRoomId(),
|
||||
messageId,
|
||||
},
|
||||
cfg,
|
||||
cfg as CoreConfig,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ export const matrixMessageActions: ChannelMessageActionAdapter = {
|
||||
userId,
|
||||
roomId: readStringParam(params, "roomId") ?? readStringParam(params, "channelId"),
|
||||
},
|
||||
cfg,
|
||||
cfg as CoreConfig,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -186,7 +186,7 @@ export const matrixMessageActions: ChannelMessageActionAdapter = {
|
||||
action: "channelInfo",
|
||||
roomId: resolveRoomId(),
|
||||
},
|
||||
cfg,
|
||||
cfg as CoreConfig,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CoreConfig } from "../types.js";
|
||||
import type { CoreConfig } from "../../types.js";
|
||||
import type { MatrixActionClient, MatrixActionClientOpts } from "./types.js";
|
||||
import { getMatrixRuntime } from "../../runtime.js";
|
||||
import { getActiveMatrixClient } from "../active-client.js";
|
||||
@@ -47,7 +47,9 @@ export async function resolveActionClient(
|
||||
if (auth.encryption && client.crypto) {
|
||||
try {
|
||||
const joinedRooms = await client.getJoinedRooms();
|
||||
await client.crypto.prepare(joinedRooms);
|
||||
await (client.crypto as { prepare: (rooms?: string[]) => Promise<void> }).prepare(
|
||||
joinedRooms,
|
||||
);
|
||||
} catch {
|
||||
// Ignore crypto prep failures for one-off actions.
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ export async function fetchEventSummary(
|
||||
eventId: string,
|
||||
): Promise<MatrixMessageSummary | null> {
|
||||
try {
|
||||
const raw = (await client.getEvent(roomId, eventId)) as MatrixRawEvent;
|
||||
const raw = (await client.getEvent(roomId, eventId)) as unknown as MatrixRawEvent;
|
||||
if (raw.unsigned?.redacted_because) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { MatrixClient } from "@vector-im/matrix-bot-sdk";
|
||||
import type { CoreConfig } from "../types.js";
|
||||
import type { CoreConfig } from "../../types.js";
|
||||
import type { MatrixAuth, MatrixResolvedConfig } from "./types.js";
|
||||
import { getMatrixRuntime } from "../../runtime.js";
|
||||
import { ensureMatrixSdkLoggingConfigured } from "./logging.js";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { MatrixClient } from "@vector-im/matrix-bot-sdk";
|
||||
import { LogService } from "@vector-im/matrix-bot-sdk";
|
||||
import type { CoreConfig } from "../types.js";
|
||||
import type { CoreConfig } from "../../types.js";
|
||||
import type { MatrixAuth } from "./types.js";
|
||||
import { resolveMatrixAuth } from "./config.js";
|
||||
import { createMatrixClient } from "./create-client.js";
|
||||
@@ -69,7 +69,9 @@ async function ensureSharedClientStarted(params: {
|
||||
try {
|
||||
const joinedRooms = await client.getJoinedRooms();
|
||||
if (client.crypto) {
|
||||
await client.crypto.prepare(joinedRooms);
|
||||
await (client.crypto as { prepare: (rooms?: string[]) => Promise<void> }).prepare(
|
||||
joinedRooms,
|
||||
);
|
||||
params.state.cryptoReady = true;
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { MatrixClient } from "@vector-im/matrix-bot-sdk";
|
||||
import type { PluginRuntime } from "openclaw/plugin-sdk";
|
||||
import type { PluginRuntime, RuntimeLogger } from "openclaw/plugin-sdk";
|
||||
import type { MatrixAuth } from "../client.js";
|
||||
import type { MatrixRawEvent } from "./types.js";
|
||||
import { EventType } from "./types.js";
|
||||
@@ -10,7 +10,7 @@ export function registerMatrixMonitorEvents(params: {
|
||||
logVerboseMessage: (message: string) => void;
|
||||
warnedEncryptedRooms: Set<string>;
|
||||
warnedCryptoMissingRooms: Set<string>;
|
||||
logger: { warn: (meta: Record<string, unknown>, message: string) => void };
|
||||
logger: RuntimeLogger;
|
||||
formatNativeDependencyHint: PluginRuntime["system"]["formatNativeDependencyHint"];
|
||||
onRoomMessage: (roomId: string, event: MatrixRawEvent) => void | Promise<void>;
|
||||
}): void {
|
||||
@@ -42,10 +42,11 @@ export function registerMatrixMonitorEvents(params: {
|
||||
client.on(
|
||||
"room.failed_decryption",
|
||||
async (roomId: string, event: MatrixRawEvent, error: Error) => {
|
||||
logger.warn(
|
||||
{ roomId, eventId: event.event_id, error: error.message },
|
||||
"Failed to decrypt message",
|
||||
);
|
||||
logger.warn("Failed to decrypt message", {
|
||||
roomId,
|
||||
eventId: event.event_id,
|
||||
error: error.message,
|
||||
});
|
||||
logVerboseMessage(
|
||||
`matrix: failed decrypt room=${roomId} id=${event.event_id ?? "unknown"} error=${error.message}`,
|
||||
);
|
||||
@@ -76,7 +77,7 @@ export function registerMatrixMonitorEvents(params: {
|
||||
warnedEncryptedRooms.add(roomId);
|
||||
const warning =
|
||||
"matrix: encrypted event received without encryption enabled; set channels.matrix.encryption=true and verify the device to decrypt";
|
||||
logger.warn({ roomId }, warning);
|
||||
logger.warn(warning, { roomId });
|
||||
}
|
||||
if (auth.encryption === true && !client.crypto && !warnedCryptoMissingRooms.has(roomId)) {
|
||||
warnedCryptoMissingRooms.add(roomId);
|
||||
@@ -86,7 +87,7 @@ export function registerMatrixMonitorEvents(params: {
|
||||
downloadCommand: "node node_modules/@matrix-org/matrix-sdk-crypto-nodejs/download-lib.js",
|
||||
});
|
||||
const warning = `matrix: encryption enabled but crypto is unavailable; ${hint}`;
|
||||
logger.warn({ roomId }, warning);
|
||||
logger.warn(warning, { roomId });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -6,9 +6,11 @@ import {
|
||||
logInboundDrop,
|
||||
logTypingFailure,
|
||||
resolveControlCommandGate,
|
||||
type PluginRuntime,
|
||||
type RuntimeEnv,
|
||||
type RuntimeLogger,
|
||||
} from "openclaw/plugin-sdk";
|
||||
import type { CoreConfig, ReplyToMode } from "../../types.js";
|
||||
import type { CoreConfig, MatrixRoomConfig, ReplyToMode } from "../../types.js";
|
||||
import type { MatrixRawEvent, RoomMessageEventContent } from "./types.js";
|
||||
import {
|
||||
formatPollAsText,
|
||||
@@ -37,34 +39,14 @@ import { EventType, RelationType } from "./types.js";
|
||||
|
||||
export type MatrixMonitorHandlerParams = {
|
||||
client: MatrixClient;
|
||||
core: {
|
||||
logging: {
|
||||
shouldLogVerbose: () => boolean;
|
||||
};
|
||||
channel: (typeof import("openclaw/plugin-sdk"))["channel"];
|
||||
system: {
|
||||
enqueueSystemEvent: (
|
||||
text: string,
|
||||
meta: { sessionKey?: string | null; contextKey?: string | null },
|
||||
) => void;
|
||||
};
|
||||
};
|
||||
core: PluginRuntime;
|
||||
cfg: CoreConfig;
|
||||
runtime: RuntimeEnv;
|
||||
logger: {
|
||||
info: (message: string | Record<string, unknown>, ...meta: unknown[]) => void;
|
||||
warn: (meta: Record<string, unknown>, message: string) => void;
|
||||
};
|
||||
logger: RuntimeLogger;
|
||||
logVerboseMessage: (message: string) => void;
|
||||
allowFrom: string[];
|
||||
roomsConfig: CoreConfig["channels"] extends { matrix?: infer MatrixConfig }
|
||||
? MatrixConfig extends { groups?: infer Groups }
|
||||
? Groups
|
||||
: Record<string, unknown> | undefined
|
||||
: Record<string, unknown> | undefined;
|
||||
mentionRegexes: ReturnType<
|
||||
(typeof import("openclaw/plugin-sdk"))["channel"]["mentions"]["buildMentionRegexes"]
|
||||
>;
|
||||
roomsConfig: Record<string, MatrixRoomConfig> | undefined;
|
||||
mentionRegexes: ReturnType<PluginRuntime["channel"]["mentions"]["buildMentionRegexes"]>;
|
||||
groupPolicy: "open" | "allowlist" | "disabled";
|
||||
replyToMode: ReplyToMode;
|
||||
threadReplies: "off" | "inbound" | "always";
|
||||
@@ -121,7 +103,7 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
|
||||
}
|
||||
|
||||
const isPollEvent = isPollStartType(eventType);
|
||||
const locationContent = event.content as LocationMessageEventContent;
|
||||
const locationContent = event.content as unknown as LocationMessageEventContent;
|
||||
const isLocationEvent =
|
||||
eventType === EventType.Location ||
|
||||
(eventType === EventType.RoomMessage && locationContent.msgtype === EventType.Location);
|
||||
@@ -159,9 +141,9 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
|
||||
const roomName = roomInfo.name;
|
||||
const roomAliases = [roomInfo.canonicalAlias ?? "", ...roomInfo.altAliases].filter(Boolean);
|
||||
|
||||
let content = event.content as RoomMessageEventContent;
|
||||
let content = event.content as unknown as RoomMessageEventContent;
|
||||
if (isPollEvent) {
|
||||
const pollStartContent = event.content as PollStartContent;
|
||||
const pollStartContent = event.content as unknown as PollStartContent;
|
||||
const pollSummary = parsePollStartContent(pollStartContent);
|
||||
if (pollSummary) {
|
||||
pollSummary.eventId = event.event_id ?? "";
|
||||
@@ -435,7 +417,7 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
|
||||
hasControlCommandInMessage;
|
||||
const canDetectMention = mentionRegexes.length > 0 || hasExplicitMention;
|
||||
if (isRoom && shouldRequireMention && !wasMentioned && !shouldBypassMention) {
|
||||
logger.info({ roomId, reason: "no-mention" }, "skipping room message");
|
||||
logger.info("skipping room message", { roomId, reason: "no-mention" });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -523,14 +505,11 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
|
||||
}
|
||||
: undefined,
|
||||
onRecordError: (err) => {
|
||||
logger.warn(
|
||||
{
|
||||
error: String(err),
|
||||
storePath,
|
||||
sessionKey: ctxPayload.SessionKey ?? route.sessionKey,
|
||||
},
|
||||
"failed updating session meta",
|
||||
);
|
||||
logger.warn("failed updating session meta", {
|
||||
error: String(err),
|
||||
storePath,
|
||||
sessionKey: ctxPayload.SessionKey ?? route.sessionKey,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ export async function monitorMatrixProvider(opts: MonitorMatrixOpts = {}): Promi
|
||||
if (!core.logging.shouldLogVerbose()) {
|
||||
return;
|
||||
}
|
||||
logger.debug(message);
|
||||
logger.debug?.(message);
|
||||
};
|
||||
|
||||
const normalizeUserEntry = (raw: string) =>
|
||||
@@ -75,13 +75,13 @@ export async function monitorMatrixProvider(opts: MonitorMatrixOpts = {}): Promi
|
||||
): Promise<string[]> => {
|
||||
let allowList = list ?? [];
|
||||
if (allowList.length === 0) {
|
||||
return allowList;
|
||||
return allowList.map(String);
|
||||
}
|
||||
const entries = allowList
|
||||
.map((entry) => normalizeUserEntry(String(entry)))
|
||||
.filter((entry) => entry && entry !== "*");
|
||||
if (entries.length === 0) {
|
||||
return allowList;
|
||||
return allowList.map(String);
|
||||
}
|
||||
const mapping: string[] = [];
|
||||
const unresolved: string[] = [];
|
||||
@@ -118,12 +118,12 @@ export async function monitorMatrixProvider(opts: MonitorMatrixOpts = {}): Promi
|
||||
`${label} entries must be full Matrix IDs (example: @user:server). Unresolved entries are ignored.`,
|
||||
);
|
||||
}
|
||||
return allowList;
|
||||
return allowList.map(String);
|
||||
};
|
||||
|
||||
const allowlistOnly = cfg.channels?.matrix?.allowlistOnly === true;
|
||||
let allowFrom = cfg.channels?.matrix?.dm?.allowFrom ?? [];
|
||||
let groupAllowFrom = cfg.channels?.matrix?.groupAllowFrom ?? [];
|
||||
let allowFrom: string[] = (cfg.channels?.matrix?.dm?.allowFrom ?? []).map(String);
|
||||
let groupAllowFrom: string[] = (cfg.channels?.matrix?.groupAllowFrom ?? []).map(String);
|
||||
let roomsConfig = cfg.channels?.matrix?.groups ?? cfg.channels?.matrix?.rooms;
|
||||
|
||||
allowFrom = await resolveUserAllowlist("matrix dm allowlist", allowFrom);
|
||||
@@ -307,15 +307,16 @@ export async function monitorMatrixProvider(opts: MonitorMatrixOpts = {}): Promi
|
||||
if (auth.encryption && client.crypto) {
|
||||
try {
|
||||
// Request verification from other sessions
|
||||
const verificationRequest = await client.crypto.requestOwnUserVerification();
|
||||
const verificationRequest = await (
|
||||
client.crypto as { requestOwnUserVerification?: () => Promise<unknown> }
|
||||
).requestOwnUserVerification?.();
|
||||
if (verificationRequest) {
|
||||
logger.info("matrix: device verification requested - please verify in another client");
|
||||
}
|
||||
} catch (err) {
|
||||
logger.debug(
|
||||
{ error: String(err) },
|
||||
"Device verification request failed (may already be verified)",
|
||||
);
|
||||
logger.debug?.("Device verification request failed (may already be verified)", {
|
||||
error: String(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,8 @@ async function fetchMatrixMediaBuffer(params: {
|
||||
|
||||
// Use the client's download method which handles auth
|
||||
try {
|
||||
const buffer = await params.client.downloadContent(params.mxcUrl);
|
||||
const result = await params.client.downloadContent(params.mxcUrl);
|
||||
const buffer = result.data;
|
||||
if (buffer.byteLength > params.maxBytes) {
|
||||
throw new Error("Matrix media exceeds configured size limit");
|
||||
}
|
||||
@@ -53,7 +54,9 @@ async function fetchEncryptedMediaBuffer(params: {
|
||||
}
|
||||
|
||||
// decryptMedia handles downloading and decrypting the encrypted content internally
|
||||
const decrypted = await params.client.crypto.decryptMedia(params.file);
|
||||
const decrypted = await params.client.crypto.decryptMedia(
|
||||
params.file as Parameters<typeof params.client.crypto.decryptMedia>[0],
|
||||
);
|
||||
|
||||
if (decrypted.byteLength > params.maxBytes) {
|
||||
throw new Error("Matrix media exceeds configured size limit");
|
||||
|
||||
@@ -73,7 +73,7 @@ export type PollSummary = {
|
||||
};
|
||||
|
||||
export function isPollStartType(eventType: string): boolean {
|
||||
return POLL_START_TYPES.includes(eventType);
|
||||
return (POLL_START_TYPES as readonly string[]).includes(eventType);
|
||||
}
|
||||
|
||||
export function getTextContent(text?: TextContent): string {
|
||||
@@ -147,7 +147,8 @@ export function buildPollStartContent(poll: PollInput): PollStartContent {
|
||||
...buildTextContent(option),
|
||||
}));
|
||||
|
||||
const maxSelections = poll.multiple ? Math.max(1, answers.length) : 1;
|
||||
const isMultiple = (poll.maxSelections ?? 1) > 1;
|
||||
const maxSelections = isMultiple ? Math.max(1, answers.length) : 1;
|
||||
const fallbackText = buildPollFallbackText(
|
||||
question,
|
||||
answers.map((answer) => getTextContent(answer)),
|
||||
@@ -156,7 +157,7 @@ export function buildPollStartContent(poll: PollInput): PollStartContent {
|
||||
return {
|
||||
[M_POLL_START]: {
|
||||
question: buildTextContent(question),
|
||||
kind: poll.multiple ? "m.poll.undisclosed" : "m.poll.disclosed",
|
||||
kind: isMultiple ? "m.poll.undisclosed" : "m.poll.disclosed",
|
||||
max_selections: maxSelections,
|
||||
answers,
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { MatrixClient } from "@vector-im/matrix-bot-sdk";
|
||||
import type { CoreConfig } from "../types.js";
|
||||
import type { CoreConfig } from "../../types.js";
|
||||
import { getMatrixRuntime } from "../../runtime.js";
|
||||
import { getActiveMatrixClient } from "../active-client.js";
|
||||
import {
|
||||
@@ -55,7 +55,9 @@ export async function resolveMatrixClient(opts: {
|
||||
if (auth.encryption && client.crypto) {
|
||||
try {
|
||||
const joinedRooms = await client.getJoinedRooms();
|
||||
await client.crypto.prepare(joinedRooms);
|
||||
await (client.crypto as { prepare: (rooms?: string[]) => Promise<void> }).prepare(
|
||||
joinedRooms,
|
||||
);
|
||||
} catch {
|
||||
// Ignore crypto prep failures for one-off sends; normal sync will retry.
|
||||
}
|
||||
|
||||
@@ -70,9 +70,12 @@ async function resolveDirectRoomId(client: MatrixClient, userId: string): Promis
|
||||
|
||||
// 1) Fast path: use account data (m.direct) for *this* logged-in user (the bot).
|
||||
try {
|
||||
const directContent = await client.getAccountData(EventType.Direct);
|
||||
const directContent = (await client.getAccountData(EventType.Direct)) as Record<
|
||||
string,
|
||||
string[] | undefined
|
||||
>;
|
||||
const list = Array.isArray(directContent?.[trimmed]) ? directContent[trimmed] : [];
|
||||
if (list.length > 0) {
|
||||
if (list && list.length > 0) {
|
||||
setDirectRoomCached(trimmed, list[0]);
|
||||
return list[0];
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export type { DmPolicy, GroupPolicy } from "openclaw/plugin-sdk";
|
||||
import type { DmPolicy, GroupPolicy } from "openclaw/plugin-sdk";
|
||||
export type { DmPolicy, GroupPolicy };
|
||||
|
||||
export type ReplyToMode = "off" | "first" | "all";
|
||||
|
||||
@@ -92,6 +93,19 @@ export type MatrixConfig = {
|
||||
export type CoreConfig = {
|
||||
channels?: {
|
||||
matrix?: MatrixConfig;
|
||||
defaults?: {
|
||||
groupPolicy?: "open" | "allowlist" | "disabled";
|
||||
};
|
||||
};
|
||||
commands?: {
|
||||
useAccessGroups?: boolean;
|
||||
};
|
||||
session?: {
|
||||
store?: string;
|
||||
};
|
||||
messages?: {
|
||||
ackReaction?: string;
|
||||
ackReactionScope?: "group-mentions" | "group-all" | "direct" | "all";
|
||||
};
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
|
||||
import {
|
||||
emptyPluginConfigSchema,
|
||||
type OpenClawPluginApi,
|
||||
type ProviderAuthContext,
|
||||
type ProviderAuthResult,
|
||||
} from "openclaw/plugin-sdk";
|
||||
import { loginMiniMaxPortalOAuth, type MiniMaxRegion } from "./oauth.js";
|
||||
|
||||
const PROVIDER_ID = "minimax-portal";
|
||||
@@ -38,8 +43,7 @@ function createOAuthHandler(region: MiniMaxRegion) {
|
||||
const defaultBaseUrl = getDefaultBaseUrl(region);
|
||||
const regionLabel = region === "cn" ? "CN" : "Global";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return async (ctx: any) => {
|
||||
return async (ctx: ProviderAuthContext): Promise<ProviderAuthResult> => {
|
||||
const progress = ctx.prompter.progress(`Starting MiniMax OAuth (${regionLabel})…`);
|
||||
try {
|
||||
const result = await loginMiniMaxPortalOAuth({
|
||||
@@ -126,7 +130,7 @@ const minimaxPortalPlugin = {
|
||||
name: "MiniMax OAuth",
|
||||
description: "OAuth flow for MiniMax models",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api) {
|
||||
register(api: OpenClawPluginApi) {
|
||||
api.registerProvider({
|
||||
id: PROVIDER_ID,
|
||||
label: PROVIDER_LABEL,
|
||||
|
||||
@@ -42,6 +42,7 @@ export const msteamsPlugin: ChannelPlugin<ResolvedMSTeamsAccount> = {
|
||||
id: "msteams",
|
||||
meta: {
|
||||
...meta,
|
||||
aliases: [...meta.aliases],
|
||||
},
|
||||
onboarding: msteamsOnboardingAdapter,
|
||||
pairing: {
|
||||
@@ -384,7 +385,8 @@ export const msteamsPlugin: ChannelPlugin<ResolvedMSTeamsAccount> = {
|
||||
if (!to) {
|
||||
return {
|
||||
isError: true,
|
||||
content: [{ type: "text", text: "Card send requires a target (to)." }],
|
||||
content: [{ type: "text" as const, text: "Card send requires a target (to)." }],
|
||||
details: { error: "Card send requires a target (to)." },
|
||||
};
|
||||
}
|
||||
const result = await sendAdaptiveCardMSTeams({
|
||||
@@ -395,7 +397,7 @@ export const msteamsPlugin: ChannelPlugin<ResolvedMSTeamsAccount> = {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
type: "text" as const,
|
||||
text: JSON.stringify({
|
||||
ok: true,
|
||||
channel: "msteams",
|
||||
@@ -404,6 +406,7 @@ export const msteamsPlugin: ChannelPlugin<ResolvedMSTeamsAccount> = {
|
||||
}),
|
||||
},
|
||||
],
|
||||
details: { ok: true, channel: "msteams", messageId: result.messageId },
|
||||
};
|
||||
}
|
||||
// Return null to fall through to default handler
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ChannelDirectoryEntry } from "openclaw/plugin-sdk";
|
||||
import type { ChannelDirectoryEntry, MSTeamsConfig } from "openclaw/plugin-sdk";
|
||||
import { GRAPH_ROOT } from "./attachments/shared.js";
|
||||
import { loadMSTeamsSdkWithAuth } from "./sdk.js";
|
||||
import { resolveMSTeamsCredentials } from "./token.js";
|
||||
@@ -62,7 +62,7 @@ async function fetchGraphJson<T>(params: {
|
||||
|
||||
async function resolveGraphToken(cfg: unknown): Promise<string> {
|
||||
const creds = resolveMSTeamsCredentials(
|
||||
(cfg as { channels?: { msteams?: unknown } })?.channels?.msteams,
|
||||
(cfg as { channels?: { msteams?: unknown } })?.channels?.msteams as MSTeamsConfig | undefined,
|
||||
);
|
||||
if (!creds) {
|
||||
throw new Error("MS Teams credentials missing");
|
||||
|
||||
@@ -49,7 +49,7 @@ async function handleFileConsentInvoke(
|
||||
|
||||
const consentResponse = parseFileConsentInvoke(activity);
|
||||
if (!consentResponse) {
|
||||
log.debug("invalid file consent invoke", { value: activity.value });
|
||||
log.debug?.("invalid file consent invoke", { value: activity.value });
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ async function handleFileConsentInvoke(
|
||||
if (consentResponse.action === "accept" && consentResponse.uploadInfo) {
|
||||
const pendingFile = getPendingUpload(uploadId);
|
||||
if (pendingFile) {
|
||||
log.debug("user accepted file consent, uploading", {
|
||||
log.debug?.("user accepted file consent, uploading", {
|
||||
uploadId,
|
||||
filename: pendingFile.filename,
|
||||
size: pendingFile.buffer.length,
|
||||
@@ -94,20 +94,20 @@ async function handleFileConsentInvoke(
|
||||
uniqueId: consentResponse.uploadInfo.uniqueId,
|
||||
});
|
||||
} catch (err) {
|
||||
log.debug("file upload failed", { uploadId, error: String(err) });
|
||||
log.debug?.("file upload failed", { uploadId, error: String(err) });
|
||||
await context.sendActivity(`File upload failed: ${String(err)}`);
|
||||
} finally {
|
||||
removePendingUpload(uploadId);
|
||||
}
|
||||
} else {
|
||||
log.debug("pending file not found for consent", { uploadId });
|
||||
log.debug?.("pending file not found for consent", { uploadId });
|
||||
await context.sendActivity(
|
||||
"The file upload request has expired. Please try sending the file again.",
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// User declined
|
||||
log.debug("user declined file consent", { uploadId });
|
||||
log.debug?.("user declined file consent", { uploadId });
|
||||
removePendingUpload(uploadId);
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ export function registerMSTeamsHandlers<T extends MSTeamsActivityHandler>(
|
||||
const membersAdded = (context as MSTeamsTurnContext).activity?.membersAdded ?? [];
|
||||
for (const member of membersAdded) {
|
||||
if (member.id !== (context as MSTeamsTurnContext).activity?.recipient?.id) {
|
||||
deps.log.debug("member added", { member: member.id });
|
||||
deps.log.debug?.("member added", { member: member.id });
|
||||
// Don't send welcome message - let the user initiate conversation.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
} from "../attachments.js";
|
||||
|
||||
type MSTeamsLogger = {
|
||||
debug: (message: string, meta?: Record<string, unknown>) => void;
|
||||
debug?: (message: string, meta?: Record<string, unknown>) => void;
|
||||
};
|
||||
|
||||
export async function resolveMSTeamsInboundMedia(params: {
|
||||
@@ -66,7 +66,7 @@ export async function resolveMSTeamsInboundMedia(params: {
|
||||
channelData: activity.channelData,
|
||||
});
|
||||
if (messageUrls.length === 0) {
|
||||
log.debug("graph message url unavailable", {
|
||||
log.debug?.("graph message url unavailable", {
|
||||
conversationType,
|
||||
hasChannelData: Boolean(activity.channelData),
|
||||
messageId: activity.id ?? undefined,
|
||||
@@ -107,16 +107,16 @@ export async function resolveMSTeamsInboundMedia(params: {
|
||||
}
|
||||
}
|
||||
if (mediaList.length === 0) {
|
||||
log.debug("graph media fetch empty", { attempts });
|
||||
log.debug?.("graph media fetch empty", { attempts });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mediaList.length > 0) {
|
||||
log.debug("downloaded attachments", { count: mediaList.length });
|
||||
log.debug?.("downloaded attachments", { count: mediaList.length });
|
||||
} else if (htmlSummary?.imgTags) {
|
||||
log.debug("inline images detected but none downloaded", {
|
||||
log.debug?.("inline images detected but none downloaded", {
|
||||
imgTags: htmlSummary.imgTags,
|
||||
srcHosts: htmlSummary.srcHosts,
|
||||
dataImages: htmlSummary.dataImages,
|
||||
|
||||
@@ -54,7 +54,7 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
|
||||
const core = getMSTeamsRuntime();
|
||||
const logVerboseMessage = (message: string) => {
|
||||
if (core.logging.shouldLogVerbose()) {
|
||||
log.debug(message);
|
||||
log.debug?.(message);
|
||||
}
|
||||
};
|
||||
const msteamsCfg = cfg.channels?.msteams;
|
||||
@@ -105,11 +105,11 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
|
||||
conversation: conversation?.id,
|
||||
});
|
||||
if (htmlSummary) {
|
||||
log.debug("html attachment summary", htmlSummary);
|
||||
log.debug?.("html attachment summary", htmlSummary);
|
||||
}
|
||||
|
||||
if (!from?.id) {
|
||||
log.debug("skipping message without from.id");
|
||||
log.debug?.("skipping message without from.id");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
|
||||
const allowFrom = dmAllowFrom;
|
||||
|
||||
if (dmPolicy === "disabled") {
|
||||
log.debug("dropping dm (dms disabled)");
|
||||
log.debug?.("dropping dm (dms disabled)");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
|
||||
});
|
||||
}
|
||||
}
|
||||
log.debug("dropping dm (not allowlisted)", {
|
||||
log.debug?.("dropping dm (not allowlisted)", {
|
||||
sender: senderId,
|
||||
label: senderName,
|
||||
allowlistMatch: formatAllowlistMatchMeta(allowMatch),
|
||||
@@ -200,7 +200,7 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
|
||||
|
||||
if (!isDirectMessage && msteamsCfg) {
|
||||
if (groupPolicy === "disabled") {
|
||||
log.debug("dropping group message (groupPolicy: disabled)", {
|
||||
log.debug?.("dropping group message (groupPolicy: disabled)", {
|
||||
conversationId,
|
||||
});
|
||||
return;
|
||||
@@ -208,7 +208,7 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
|
||||
|
||||
if (groupPolicy === "allowlist") {
|
||||
if (channelGate.allowlistConfigured && !channelGate.allowed) {
|
||||
log.debug("dropping group message (not in team/channel allowlist)", {
|
||||
log.debug?.("dropping group message (not in team/channel allowlist)", {
|
||||
conversationId,
|
||||
teamKey: channelGate.teamKey ?? "none",
|
||||
channelKey: channelGate.channelKey ?? "none",
|
||||
@@ -218,20 +218,19 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
|
||||
return;
|
||||
}
|
||||
if (effectiveGroupAllowFrom.length === 0 && !channelGate.allowlistConfigured) {
|
||||
log.debug("dropping group message (groupPolicy: allowlist, no allowlist)", {
|
||||
log.debug?.("dropping group message (groupPolicy: allowlist, no allowlist)", {
|
||||
conversationId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (effectiveGroupAllowFrom.length > 0) {
|
||||
const allowMatch = resolveMSTeamsAllowlistMatch({
|
||||
groupPolicy,
|
||||
allowFrom: effectiveGroupAllowFrom,
|
||||
senderId,
|
||||
senderName,
|
||||
});
|
||||
if (!allowMatch.allowed) {
|
||||
log.debug("dropping group message (not in groupAllowFrom)", {
|
||||
log.debug?.("dropping group message (not in groupAllowFrom)", {
|
||||
sender: senderId,
|
||||
label: senderName,
|
||||
allowlistMatch: formatAllowlistMatchMeta(allowMatch),
|
||||
@@ -293,7 +292,7 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
|
||||
locale: activity.locale,
|
||||
};
|
||||
conversationStore.upsert(conversationId, conversationRef).catch((err) => {
|
||||
log.debug("failed to save conversation reference", {
|
||||
log.debug?.("failed to save conversation reference", {
|
||||
error: formatUnknownError(err),
|
||||
});
|
||||
});
|
||||
@@ -307,7 +306,7 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
|
||||
selections: pollVote.selections,
|
||||
});
|
||||
if (!poll) {
|
||||
log.debug("poll vote ignored (poll not found)", {
|
||||
log.debug?.("poll vote ignored (poll not found)", {
|
||||
pollId: pollVote.pollId,
|
||||
});
|
||||
} else {
|
||||
@@ -327,7 +326,7 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
|
||||
}
|
||||
|
||||
if (!rawBody) {
|
||||
log.debug("skipping empty message after stripping mentions");
|
||||
log.debug?.("skipping empty message after stripping mentions");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -377,7 +376,7 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
|
||||
});
|
||||
const mentioned = mentionGate.effectiveWasMentioned;
|
||||
if (requireMention && mentionGate.shouldSkip) {
|
||||
log.debug("skipping message (mention required)", {
|
||||
log.debug?.("skipping message (mention required)", {
|
||||
teamId,
|
||||
channelId,
|
||||
requireMention,
|
||||
@@ -413,7 +412,8 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
|
||||
channelData: activity.channelData,
|
||||
},
|
||||
log,
|
||||
preserveFilenames: cfg.media?.preserveFilenames,
|
||||
preserveFilenames: (cfg as { media?: { preserveFilenames?: boolean } }).media
|
||||
?.preserveFilenames,
|
||||
});
|
||||
|
||||
const mediaPayload = buildMSTeamsMediaPayload(mediaList);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export type MSTeamsMonitorLogger = {
|
||||
debug: (message: string, meta?: Record<string, unknown>) => void;
|
||||
debug?: (message: string, meta?: Record<string, unknown>) => void;
|
||||
info: (message: string, meta?: Record<string, unknown>) => void;
|
||||
error: (message: string, meta?: Record<string, unknown>) => void;
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ import type { MSTeamsConversationStore } from "./conversation-store.js";
|
||||
import type { MSTeamsAdapter } from "./messenger.js";
|
||||
import { createMSTeamsConversationStoreFs } from "./conversation-store-fs.js";
|
||||
import { formatUnknownError } from "./errors.js";
|
||||
import { registerMSTeamsHandlers } from "./monitor-handler.js";
|
||||
import { registerMSTeamsHandlers, type MSTeamsActivityHandler } from "./monitor-handler.js";
|
||||
import { createMSTeamsPollStoreFs, type MSTeamsPollStore } from "./polls.js";
|
||||
import {
|
||||
resolveMSTeamsChannelAllowlist,
|
||||
@@ -40,7 +40,7 @@ export async function monitorMSTeamsProvider(
|
||||
let cfg = opts.cfg;
|
||||
let msteamsCfg = cfg.channels?.msteams;
|
||||
if (!msteamsCfg?.enabled) {
|
||||
log.debug("msteams provider disabled");
|
||||
log.debug?.("msteams provider disabled");
|
||||
return { app: null, shutdown: async () => {} };
|
||||
}
|
||||
|
||||
@@ -224,7 +224,7 @@ export async function monitorMSTeamsProvider(
|
||||
const tokenProvider = new MsalTokenProvider(authConfig);
|
||||
const adapter = createMSTeamsAdapter(authConfig, sdk);
|
||||
|
||||
const handler = registerMSTeamsHandlers(new ActivityHandler(), {
|
||||
const handler = registerMSTeamsHandlers(new ActivityHandler() as MSTeamsActivityHandler, {
|
||||
cfg,
|
||||
runtime,
|
||||
appId,
|
||||
@@ -246,7 +246,7 @@ export async function monitorMSTeamsProvider(
|
||||
const configuredPath = msteamsCfg.webhook?.path ?? "/api/messages";
|
||||
const messageHandler = (req: Request, res: Response) => {
|
||||
void adapter
|
||||
.process(req, res, (context: unknown) => handler.run(context))
|
||||
.process(req, res, (context: unknown) => handler.run!(context))
|
||||
.catch((err: unknown) => {
|
||||
log.error("msteams webhook failed", { error: formatUnknownError(err) });
|
||||
});
|
||||
@@ -258,7 +258,7 @@ export async function monitorMSTeamsProvider(
|
||||
expressApp.post("/api/messages", messageHandler);
|
||||
}
|
||||
|
||||
log.debug("listening on paths", {
|
||||
log.debug?.("listening on paths", {
|
||||
primary: configuredPath,
|
||||
fallback: "/api/messages",
|
||||
});
|
||||
@@ -277,7 +277,7 @@ export async function monitorMSTeamsProvider(
|
||||
return new Promise<void>((resolve) => {
|
||||
httpServer.close((err) => {
|
||||
if (err) {
|
||||
log.debug("msteams server close error", { error: String(err) });
|
||||
log.debug?.("msteams server close error", { error: String(err) });
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import type {
|
||||
OpenClawConfig,
|
||||
DmPolicy,
|
||||
WizardPrompter,
|
||||
MSTeamsTeamConfig,
|
||||
} from "openclaw/plugin-sdk";
|
||||
import {
|
||||
addWildcardAllowFrom,
|
||||
@@ -184,7 +185,7 @@ function setMSTeamsTeamsAllowlist(
|
||||
msteams: {
|
||||
...cfg.channels?.msteams,
|
||||
enabled: true,
|
||||
teams,
|
||||
teams: teams as Record<string, MSTeamsTeamConfig>,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -49,7 +49,7 @@ export function createMSTeamsReplyDispatcher(params: {
|
||||
start: sendTypingIndicator,
|
||||
onStartError: (err) => {
|
||||
logTypingFailure({
|
||||
log: (message) => params.log.debug(message),
|
||||
log: (message) => params.log.debug?.(message),
|
||||
channel: "msteams",
|
||||
action: "start",
|
||||
error: err,
|
||||
@@ -94,7 +94,7 @@ export function createMSTeamsReplyDispatcher(params: {
|
||||
// Enable default retry/backoff for throttling/transient failures.
|
||||
retry: {},
|
||||
onRetry: (event) => {
|
||||
params.log.debug("retrying send", {
|
||||
params.log.debug?.("retrying send", {
|
||||
replyStyle: params.replyStyle,
|
||||
...event,
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { MSTeamsConfig } from "openclaw/plugin-sdk";
|
||||
import { GRAPH_ROOT } from "./attachments/shared.js";
|
||||
import { loadMSTeamsSdkWithAuth } from "./sdk.js";
|
||||
import { resolveMSTeamsCredentials } from "./token.js";
|
||||
@@ -155,7 +156,7 @@ async function fetchGraphJson<T>(params: {
|
||||
|
||||
async function resolveGraphToken(cfg: unknown): Promise<string> {
|
||||
const creds = resolveMSTeamsCredentials(
|
||||
(cfg as { channels?: { msteams?: unknown } })?.channels?.msteams,
|
||||
(cfg as { channels?: { msteams?: unknown } })?.channels?.msteams as MSTeamsConfig | undefined,
|
||||
);
|
||||
if (!creds) {
|
||||
throw new Error("MS Teams credentials missing");
|
||||
|
||||
@@ -111,7 +111,7 @@ export async function sendMessageMSTeams(
|
||||
sharePointSiteId,
|
||||
} = ctx;
|
||||
|
||||
log.debug("sending proactive message", {
|
||||
log.debug?.("sending proactive message", {
|
||||
conversationId,
|
||||
conversationType,
|
||||
textLength: messageText.length,
|
||||
@@ -131,7 +131,7 @@ export async function sendMessageMSTeams(
|
||||
const fallbackFileName = await extractFilename(mediaUrl);
|
||||
const fileName = media.fileName ?? fallbackFileName;
|
||||
|
||||
log.debug("processing media", {
|
||||
log.debug?.("processing media", {
|
||||
fileName,
|
||||
contentType: media.contentType,
|
||||
size: media.buffer.length,
|
||||
@@ -155,7 +155,7 @@ export async function sendMessageMSTeams(
|
||||
description: messageText || undefined,
|
||||
});
|
||||
|
||||
log.debug("sending file consent card", { uploadId, fileName, size: media.buffer.length });
|
||||
log.debug?.("sending file consent card", { uploadId, fileName, size: media.buffer.length });
|
||||
|
||||
const baseRef = buildConversationReference(ref);
|
||||
const proactiveRef = { ...baseRef, activityId: undefined };
|
||||
@@ -205,7 +205,7 @@ export async function sendMessageMSTeams(
|
||||
try {
|
||||
if (sharePointSiteId) {
|
||||
// Use SharePoint upload + Graph API for native file card
|
||||
log.debug("uploading to SharePoint for native file card", {
|
||||
log.debug?.("uploading to SharePoint for native file card", {
|
||||
fileName,
|
||||
conversationType,
|
||||
siteId: sharePointSiteId,
|
||||
@@ -221,7 +221,7 @@ export async function sendMessageMSTeams(
|
||||
usePerUserSharing: conversationType === "groupChat",
|
||||
});
|
||||
|
||||
log.debug("SharePoint upload complete", {
|
||||
log.debug?.("SharePoint upload complete", {
|
||||
itemId: uploaded.itemId,
|
||||
shareUrl: uploaded.shareUrl,
|
||||
});
|
||||
@@ -233,7 +233,7 @@ export async function sendMessageMSTeams(
|
||||
tokenProvider,
|
||||
});
|
||||
|
||||
log.debug("driveItem properties retrieved", {
|
||||
log.debug?.("driveItem properties retrieved", {
|
||||
eTag: driveItem.eTag,
|
||||
webDavUrl: driveItem.webDavUrl,
|
||||
});
|
||||
@@ -265,7 +265,7 @@ export async function sendMessageMSTeams(
|
||||
}
|
||||
|
||||
// Fallback: no SharePoint site configured, use OneDrive with markdown link
|
||||
log.debug("uploading to OneDrive (no SharePoint site configured)", {
|
||||
log.debug?.("uploading to OneDrive (no SharePoint site configured)", {
|
||||
fileName,
|
||||
conversationType,
|
||||
});
|
||||
@@ -277,7 +277,7 @@ export async function sendMessageMSTeams(
|
||||
tokenProvider,
|
||||
});
|
||||
|
||||
log.debug("OneDrive upload complete", {
|
||||
log.debug?.("OneDrive upload complete", {
|
||||
itemId: uploaded.itemId,
|
||||
shareUrl: uploaded.shareUrl,
|
||||
});
|
||||
@@ -349,7 +349,7 @@ async function sendTextWithMedia(
|
||||
messages: [{ text: text || undefined, mediaUrl }],
|
||||
retry: {},
|
||||
onRetry: (event) => {
|
||||
log.debug("retrying send", { conversationId, ...event });
|
||||
log.debug?.("retrying send", { conversationId, ...event });
|
||||
},
|
||||
tokenProvider,
|
||||
sharePointSiteId,
|
||||
@@ -392,7 +392,7 @@ export async function sendPollMSTeams(
|
||||
maxSelections,
|
||||
});
|
||||
|
||||
log.debug("sending poll", {
|
||||
log.debug?.("sending poll", {
|
||||
conversationId,
|
||||
pollId: pollCard.pollId,
|
||||
optionCount: pollCard.options.length,
|
||||
@@ -452,7 +452,7 @@ export async function sendAdaptiveCardMSTeams(
|
||||
to,
|
||||
});
|
||||
|
||||
log.debug("sending adaptive card", {
|
||||
log.debug?.("sending adaptive card", {
|
||||
conversationId,
|
||||
cardType: card.type,
|
||||
cardVersion: card.version,
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
type RuntimeEnv,
|
||||
} from "openclaw/plugin-sdk";
|
||||
import type { ResolvedNextcloudTalkAccount } from "./accounts.js";
|
||||
import type { CoreConfig, NextcloudTalkInboundMessage } from "./types.js";
|
||||
import type { CoreConfig, GroupPolicy, NextcloudTalkInboundMessage } from "./types.js";
|
||||
import {
|
||||
normalizeNextcloudTalkAllowlist,
|
||||
resolveNextcloudTalkAllowlistMatch,
|
||||
@@ -84,8 +84,12 @@ export async function handleNextcloudTalkInbound(params: {
|
||||
statusSink?.({ lastInboundAt: message.timestamp });
|
||||
|
||||
const dmPolicy = account.config.dmPolicy ?? "pairing";
|
||||
const defaultGroupPolicy = config.channels?.defaults?.groupPolicy;
|
||||
const groupPolicy = account.config.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
|
||||
const defaultGroupPolicy = (config.channels as Record<string, unknown> | undefined)?.defaults as
|
||||
| { groupPolicy?: string }
|
||||
| undefined;
|
||||
const groupPolicy = (account.config.groupPolicy ??
|
||||
defaultGroupPolicy?.groupPolicy ??
|
||||
"allowlist") as GroupPolicy;
|
||||
|
||||
const configAllowFrom = normalizeNextcloudTalkAllowlist(account.config.allowFrom);
|
||||
const configGroupAllowFrom = normalizeNextcloudTalkAllowlist(account.config.groupAllowFrom);
|
||||
@@ -118,7 +122,8 @@ export async function handleNextcloudTalkInbound(params: {
|
||||
cfg: config as OpenClawConfig,
|
||||
surface: CHANNEL_ID,
|
||||
});
|
||||
const useAccessGroups = config.commands?.useAccessGroups !== false;
|
||||
const useAccessGroups =
|
||||
(config.commands as Record<string, unknown> | undefined)?.useAccessGroups !== false;
|
||||
const senderAllowedForCommands = resolveNextcloudTalkAllowlistMatch({
|
||||
allowFrom: isGroup ? effectiveGroupAllowFrom : effectiveAllowFrom,
|
||||
senderId,
|
||||
@@ -234,9 +239,12 @@ export async function handleNextcloudTalkInbound(params: {
|
||||
});
|
||||
|
||||
const fromLabel = isGroup ? `room:${roomName || roomToken}` : senderName || `user:${senderId}`;
|
||||
const storePath = core.channel.session.resolveStorePath(config.session?.store, {
|
||||
agentId: route.agentId,
|
||||
});
|
||||
const storePath = core.channel.session.resolveStorePath(
|
||||
(config.session as Record<string, unknown> | undefined)?.store as string | undefined,
|
||||
{
|
||||
agentId: route.agentId,
|
||||
},
|
||||
);
|
||||
const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(config as OpenClawConfig);
|
||||
const previousTimestamp = core.channel.session.readSessionUpdatedAt({
|
||||
storePath,
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
normalizeAccountId,
|
||||
type ChannelOnboardingAdapter,
|
||||
type ChannelOnboardingDmPolicy,
|
||||
type OpenClawConfig,
|
||||
type WizardPrompter,
|
||||
} from "openclaw/plugin-sdk";
|
||||
import type { CoreConfig, DmPolicy } from "./types.js";
|
||||
@@ -159,7 +160,11 @@ const dmPolicy: ChannelOnboardingDmPolicy = {
|
||||
allowFromKey: "channels.nextcloud-talk.allowFrom",
|
||||
getCurrent: (cfg) => cfg.channels?.["nextcloud-talk"]?.dmPolicy ?? "pairing",
|
||||
setPolicy: (cfg, policy) => setNextcloudTalkDmPolicy(cfg as CoreConfig, policy as DmPolicy),
|
||||
promptAllowFrom: promptNextcloudTalkAllowFromForAccount,
|
||||
promptAllowFrom: promptNextcloudTalkAllowFromForAccount as (params: {
|
||||
cfg: OpenClawConfig;
|
||||
prompter: WizardPrompter;
|
||||
accountId?: string | undefined;
|
||||
}) => Promise<OpenClawConfig>,
|
||||
};
|
||||
|
||||
export const nextcloudTalkOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
@@ -196,7 +201,7 @@ export const nextcloudTalkOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
prompter,
|
||||
label: "Nextcloud Talk",
|
||||
currentId: accountId,
|
||||
listAccountIds: listNextcloudTalkAccountIds,
|
||||
listAccountIds: listNextcloudTalkAccountIds as (cfg: OpenClawConfig) => string[],
|
||||
defaultAccountId,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import type {
|
||||
GroupPolicy,
|
||||
} from "openclaw/plugin-sdk";
|
||||
|
||||
export type { DmPolicy, GroupPolicy };
|
||||
|
||||
export type NextcloudTalkRoomConfig = {
|
||||
requireMention?: boolean;
|
||||
/** Optional tool policy overrides for this room. */
|
||||
|
||||
@@ -148,7 +148,11 @@ export const nostrPlugin: ChannelPlugin<ResolvedNostrAccount> = {
|
||||
const message = core.channel.text.convertMarkdownTables(text ?? "", tableMode);
|
||||
const normalizedTo = normalizePubkey(to);
|
||||
await bus.sendDm(normalizedTo, message);
|
||||
return { channel: "nostr", to: normalizedTo };
|
||||
return {
|
||||
channel: "nostr" as const,
|
||||
to: normalizedTo,
|
||||
messageId: `nostr-${Date.now()}`,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
@@ -224,10 +228,15 @@ export const nostrPlugin: ChannelPlugin<ResolvedNostrAccount> = {
|
||||
privateKey: account.privateKey,
|
||||
relays: account.relays,
|
||||
onMessage: async (senderPubkey, text, reply) => {
|
||||
ctx.log?.debug(`[${account.accountId}] DM from ${senderPubkey}: ${text.slice(0, 50)}...`);
|
||||
ctx.log?.debug?.(
|
||||
`[${account.accountId}] DM from ${senderPubkey}: ${text.slice(0, 50)}...`,
|
||||
);
|
||||
|
||||
// Forward to OpenClaw's message pipeline
|
||||
await runtime.channel.reply.handleInboundMessage({
|
||||
// TODO: Replace with proper dispatchReplyWithBufferedBlockDispatcher call
|
||||
await (
|
||||
runtime.channel.reply as { handleInboundMessage?: (params: unknown) => Promise<void> }
|
||||
).handleInboundMessage?.({
|
||||
channel: "nostr",
|
||||
accountId: account.accountId,
|
||||
senderId: senderPubkey,
|
||||
@@ -240,31 +249,33 @@ export const nostrPlugin: ChannelPlugin<ResolvedNostrAccount> = {
|
||||
});
|
||||
},
|
||||
onError: (error, context) => {
|
||||
ctx.log?.error(`[${account.accountId}] Nostr error (${context}): ${error.message}`);
|
||||
ctx.log?.error?.(`[${account.accountId}] Nostr error (${context}): ${error.message}`);
|
||||
},
|
||||
onConnect: (relay) => {
|
||||
ctx.log?.debug(`[${account.accountId}] Connected to relay: ${relay}`);
|
||||
ctx.log?.debug?.(`[${account.accountId}] Connected to relay: ${relay}`);
|
||||
},
|
||||
onDisconnect: (relay) => {
|
||||
ctx.log?.debug(`[${account.accountId}] Disconnected from relay: ${relay}`);
|
||||
ctx.log?.debug?.(`[${account.accountId}] Disconnected from relay: ${relay}`);
|
||||
},
|
||||
onEose: (relays) => {
|
||||
ctx.log?.debug(`[${account.accountId}] EOSE received from relays: ${relays}`);
|
||||
ctx.log?.debug?.(`[${account.accountId}] EOSE received from relays: ${relays}`);
|
||||
},
|
||||
onMetric: (event: MetricEvent) => {
|
||||
// Log significant metrics at appropriate levels
|
||||
if (event.name.startsWith("event.rejected.")) {
|
||||
ctx.log?.debug(`[${account.accountId}] Metric: ${event.name}`, event.labels);
|
||||
ctx.log?.debug?.(
|
||||
`[${account.accountId}] Metric: ${event.name} ${JSON.stringify(event.labels)}`,
|
||||
);
|
||||
} else if (event.name === "relay.circuit_breaker.open") {
|
||||
ctx.log?.warn(
|
||||
ctx.log?.warn?.(
|
||||
`[${account.accountId}] Circuit breaker opened for relay: ${event.labels?.relay}`,
|
||||
);
|
||||
} else if (event.name === "relay.circuit_breaker.close") {
|
||||
ctx.log?.info(
|
||||
ctx.log?.info?.(
|
||||
`[${account.accountId}] Circuit breaker closed for relay: ${event.labels?.relay}`,
|
||||
);
|
||||
} else if (event.name === "relay.error") {
|
||||
ctx.log?.debug(`[${account.accountId}] Relay error: ${event.labels?.relay}`);
|
||||
ctx.log?.debug?.(`[${account.accountId}] Relay error: ${event.labels?.relay}`);
|
||||
}
|
||||
// Update cached metrics snapshot
|
||||
if (busHandle) {
|
||||
|
||||
@@ -488,24 +488,28 @@ export async function startNostrBus(options: NostrBusOptions): Promise<NostrBusH
|
||||
}
|
||||
}
|
||||
|
||||
const sub = pool.subscribeMany(relays, [{ kinds: [4], "#p": [pk], since }], {
|
||||
onevent: handleEvent,
|
||||
oneose: () => {
|
||||
// EOSE handler - called when all stored events have been received
|
||||
for (const relay of relays) {
|
||||
metrics.emit("relay.message.eose", 1, { relay });
|
||||
}
|
||||
onEose?.(relays.join(", "));
|
||||
const sub = pool.subscribeMany(
|
||||
relays,
|
||||
[{ kinds: [4], "#p": [pk], since }] as unknown as Parameters<typeof pool.subscribeMany>[1],
|
||||
{
|
||||
onevent: handleEvent,
|
||||
oneose: () => {
|
||||
// EOSE handler - called when all stored events have been received
|
||||
for (const relay of relays) {
|
||||
metrics.emit("relay.message.eose", 1, { relay });
|
||||
}
|
||||
onEose?.(relays.join(", "));
|
||||
},
|
||||
onclose: (reason) => {
|
||||
// Handle subscription close
|
||||
for (const relay of relays) {
|
||||
metrics.emit("relay.message.closed", 1, { relay });
|
||||
options.onDisconnect?.(relay);
|
||||
}
|
||||
onError?.(new Error(`Subscription closed: ${reason.join(", ")}`), "subscription");
|
||||
},
|
||||
},
|
||||
onclose: (reason) => {
|
||||
// Handle subscription close
|
||||
for (const relay of relays) {
|
||||
metrics.emit("relay.message.closed", 1, { relay });
|
||||
options.onDisconnect?.(relay);
|
||||
}
|
||||
onError?.(new Error(`Subscription closed: ${reason.join(", ")}`), "subscription");
|
||||
},
|
||||
});
|
||||
);
|
||||
|
||||
// Public sendDm function
|
||||
const sendDm = async (toPubkey: string, text: string): Promise<void> => {
|
||||
@@ -693,7 +697,7 @@ export function normalizePubkey(input: string): string {
|
||||
throw new Error("Invalid npub key");
|
||||
}
|
||||
// Convert Uint8Array to hex string
|
||||
return Array.from(decoded.data)
|
||||
return Array.from(decoded.data as unknown as Uint8Array)
|
||||
.map((b) => b.toString(16).padStart(2, "0"))
|
||||
.join("");
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ export async function importProfileFromRelays(
|
||||
authors: [pubkey],
|
||||
limit: 1,
|
||||
},
|
||||
],
|
||||
] as unknown as Parameters<typeof pool.subscribeMany>[1],
|
||||
{
|
||||
onevent(event) {
|
||||
events.push({ event, relay });
|
||||
|
||||
@@ -92,7 +92,8 @@ function resolveStatePath(stateDir: string): string {
|
||||
async function readArmState(statePath: string): Promise<ArmStateFile | null> {
|
||||
try {
|
||||
const raw = await fs.readFile(statePath, "utf8");
|
||||
const parsed = JSON.parse(raw) as Partial<ArmStateFile>;
|
||||
// Type as unknown record first to allow property access during validation
|
||||
const parsed = JSON.parse(raw) as Record<string, unknown>;
|
||||
if (parsed.version !== 1 && parsed.version !== 2) {
|
||||
return null;
|
||||
}
|
||||
@@ -106,11 +107,11 @@ async function readArmState(statePath: string): Promise<ArmStateFile | null> {
|
||||
if (parsed.version === 1) {
|
||||
if (
|
||||
!Array.isArray(parsed.removedFromDeny) ||
|
||||
!parsed.removedFromDeny.every((v) => typeof v === "string")
|
||||
!parsed.removedFromDeny.every((v: unknown) => typeof v === "string")
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return parsed as ArmStateFile;
|
||||
return parsed as unknown as ArmStateFile;
|
||||
}
|
||||
|
||||
const group = typeof parsed.group === "string" ? parsed.group : "";
|
||||
@@ -119,23 +120,23 @@ async function readArmState(statePath: string): Promise<ArmStateFile | null> {
|
||||
}
|
||||
if (
|
||||
!Array.isArray(parsed.armedCommands) ||
|
||||
!parsed.armedCommands.every((v) => typeof v === "string")
|
||||
!parsed.armedCommands.every((v: unknown) => typeof v === "string")
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
if (
|
||||
!Array.isArray(parsed.addedToAllow) ||
|
||||
!parsed.addedToAllow.every((v) => typeof v === "string")
|
||||
!parsed.addedToAllow.every((v: unknown) => typeof v === "string")
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
if (
|
||||
!Array.isArray(parsed.removedFromDeny) ||
|
||||
!parsed.removedFromDeny.every((v) => typeof v === "string")
|
||||
!parsed.removedFromDeny.every((v: unknown) => typeof v === "string")
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return parsed as ArmStateFile;
|
||||
return parsed as unknown as ArmStateFile;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
|
||||
import {
|
||||
emptyPluginConfigSchema,
|
||||
type OpenClawPluginApi,
|
||||
type ProviderAuthContext,
|
||||
} from "openclaw/plugin-sdk";
|
||||
import { loginQwenPortalOAuth } from "./oauth.js";
|
||||
|
||||
const PROVIDER_ID = "qwen-portal";
|
||||
@@ -36,7 +40,7 @@ const qwenPortalPlugin = {
|
||||
name: "Qwen OAuth",
|
||||
description: "OAuth flow for Qwen (free-tier) models",
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api) {
|
||||
register(api: OpenClawPluginApi) {
|
||||
api.registerProvider({
|
||||
id: PROVIDER_ID,
|
||||
label: PROVIDER_LABEL,
|
||||
@@ -48,7 +52,7 @@ const qwenPortalPlugin = {
|
||||
label: "Qwen OAuth",
|
||||
hint: "Device code login",
|
||||
kind: "device_code",
|
||||
run: async (ctx) => {
|
||||
run: async (ctx: ProviderAuthContext) => {
|
||||
const progress = ctx.prompter.progress("Starting Qwen OAuth…");
|
||||
try {
|
||||
const result = await loginQwenPortalOAuth({
|
||||
|
||||
@@ -25,10 +25,16 @@ import {
|
||||
import { getSignalRuntime } from "./runtime.js";
|
||||
|
||||
const signalMessageActions: ChannelMessageActionAdapter = {
|
||||
listActions: (ctx) => getSignalRuntime().channel.signal.messageActions.listActions(ctx),
|
||||
supportsAction: (ctx) => getSignalRuntime().channel.signal.messageActions.supportsAction?.(ctx),
|
||||
handleAction: async (ctx) =>
|
||||
await getSignalRuntime().channel.signal.messageActions.handleAction(ctx),
|
||||
listActions: (ctx) => getSignalRuntime().channel.signal.messageActions?.listActions?.(ctx) ?? [],
|
||||
supportsAction: (ctx) =>
|
||||
getSignalRuntime().channel.signal.messageActions?.supportsAction?.(ctx) ?? false,
|
||||
handleAction: async (ctx) => {
|
||||
const ma = getSignalRuntime().channel.signal.messageActions;
|
||||
if (!ma?.handleAction) {
|
||||
throw new Error("Signal message actions not available");
|
||||
}
|
||||
return ma.handleAction(ctx);
|
||||
},
|
||||
};
|
||||
|
||||
const meta = getChatChannelMeta("signal");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
||||
import type { ChannelPlugin, OpenClawPluginApi } from "openclaw/plugin-sdk";
|
||||
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
|
||||
import { telegramPlugin } from "./src/channel.js";
|
||||
import { setTelegramRuntime } from "./src/runtime.js";
|
||||
@@ -10,7 +10,7 @@ const plugin = {
|
||||
configSchema: emptyPluginConfigSchema(),
|
||||
register(api: OpenClawPluginApi) {
|
||||
setTelegramRuntime(api.runtime);
|
||||
api.registerChannel({ plugin: telegramPlugin });
|
||||
api.registerChannel({ plugin: telegramPlugin as ChannelPlugin });
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -32,11 +32,17 @@ import { getTelegramRuntime } from "./runtime.js";
|
||||
const meta = getChatChannelMeta("telegram");
|
||||
|
||||
const telegramMessageActions: ChannelMessageActionAdapter = {
|
||||
listActions: (ctx) => getTelegramRuntime().channel.telegram.messageActions.listActions(ctx),
|
||||
listActions: (ctx) =>
|
||||
getTelegramRuntime().channel.telegram.messageActions?.listActions?.(ctx) ?? [],
|
||||
extractToolSend: (ctx) =>
|
||||
getTelegramRuntime().channel.telegram.messageActions.extractToolSend(ctx),
|
||||
handleAction: async (ctx) =>
|
||||
await getTelegramRuntime().channel.telegram.messageActions.handleAction(ctx),
|
||||
getTelegramRuntime().channel.telegram.messageActions?.extractToolSend?.(ctx) ?? null,
|
||||
handleAction: async (ctx) => {
|
||||
const ma = getTelegramRuntime().channel.telegram.messageActions;
|
||||
if (!ma?.handleAction) {
|
||||
throw new Error("Telegram message actions not available");
|
||||
}
|
||||
return ma.handleAction(ctx);
|
||||
},
|
||||
};
|
||||
|
||||
function parseReplyToMessageId(replyToId?: string | null) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type {
|
||||
ChannelAccountSnapshot,
|
||||
ChannelOutboundAdapter,
|
||||
ChannelPlugin,
|
||||
ChannelSetupInput,
|
||||
@@ -154,7 +155,7 @@ const tlonOutbound: ChannelOutboundAdapter = {
|
||||
},
|
||||
sendMedia: async ({ cfg, to, text, mediaUrl, accountId, replyToId, threadId }) => {
|
||||
const mergedText = buildMediaText(text, mediaUrl);
|
||||
return await tlonOutbound.sendText({
|
||||
return await tlonOutbound.sendText!({
|
||||
cfg,
|
||||
to,
|
||||
text: mergedText,
|
||||
@@ -224,9 +225,11 @@ export const tlonPlugin: ChannelPlugin = {
|
||||
deleteAccount: ({ cfg, accountId }) => {
|
||||
const useDefault = !accountId || accountId === "default";
|
||||
if (useDefault) {
|
||||
// @ts-expect-error
|
||||
// oxlint-disable-next-line no-unused-vars
|
||||
const { ship, code, url, name, ...rest } = cfg.channels?.tlon ?? {};
|
||||
const { ship, code, url, name, ...rest } = (cfg.channels?.tlon ?? {}) as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
return {
|
||||
...cfg,
|
||||
channels: {
|
||||
@@ -235,9 +238,9 @@ export const tlonPlugin: ChannelPlugin = {
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
}
|
||||
// @ts-expect-error
|
||||
// oxlint-disable-next-line no-unused-vars
|
||||
const { [accountId]: removed, ...remainingAccounts } = cfg.channels?.tlon?.accounts ?? {};
|
||||
const { [accountId]: removed, ...remainingAccounts } = (cfg.channels?.tlon?.accounts ??
|
||||
{}) as Record<string, unknown>;
|
||||
return {
|
||||
...cfg,
|
||||
channels: {
|
||||
@@ -334,8 +337,8 @@ export const tlonPlugin: ChannelPlugin = {
|
||||
},
|
||||
buildChannelSummary: ({ snapshot }) => ({
|
||||
configured: snapshot.configured ?? false,
|
||||
ship: snapshot.ship ?? null,
|
||||
url: snapshot.url ?? null,
|
||||
ship: (snapshot as { ship?: string | null }).ship ?? null,
|
||||
url: (snapshot as { url?: string | null }).url ?? null,
|
||||
}),
|
||||
probeAccount: async ({ account }) => {
|
||||
if (!account.configured || !account.ship || !account.url || !account.code) {
|
||||
@@ -356,7 +359,7 @@ export const tlonPlugin: ChannelPlugin = {
|
||||
await api.delete();
|
||||
}
|
||||
} catch (error) {
|
||||
return { ok: false, error: error?.message ?? String(error) };
|
||||
return { ok: false, error: (error as { message?: string })?.message ?? String(error) };
|
||||
}
|
||||
},
|
||||
buildAccountSnapshot: ({ account, runtime, probe }) => ({
|
||||
@@ -380,7 +383,7 @@ export const tlonPlugin: ChannelPlugin = {
|
||||
accountId: account.accountId,
|
||||
ship: account.ship,
|
||||
url: account.url,
|
||||
});
|
||||
} as ChannelAccountSnapshot);
|
||||
ctx.log?.info(`[${account.accountId}] starting Tlon provider for ${account.ship ?? "tlon"}`);
|
||||
return monitorTlonProvider({
|
||||
runtime: ctx.runtime,
|
||||
|
||||
@@ -17,7 +17,7 @@ export async function fetchGroupChanges(
|
||||
return null;
|
||||
} catch (error) {
|
||||
runtime.log?.(
|
||||
`[tlon] Failed to fetch changes (falling back to full init): ${error?.message ?? String(error)}`,
|
||||
`[tlon] Failed to fetch changes (falling back to full init): ${(error as { message?: string })?.message ?? String(error)}`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
@@ -66,7 +66,9 @@ export async function fetchAllChannels(
|
||||
|
||||
return channels;
|
||||
} catch (error) {
|
||||
runtime.log?.(`[tlon] Auto-discovery failed: ${error?.message ?? String(error)}`);
|
||||
runtime.log?.(
|
||||
`[tlon] Auto-discovery failed: ${(error as { message?: string })?.message ?? String(error)}`,
|
||||
);
|
||||
runtime.log?.(
|
||||
"[tlon] To monitor group channels, add them to config: channels.tlon.groupChannels",
|
||||
);
|
||||
|
||||
@@ -68,7 +68,9 @@ export async function fetchChannelHistory(
|
||||
runtime?.log?.(`[tlon] Extracted ${messages.length} messages from history`);
|
||||
return messages;
|
||||
} catch (error) {
|
||||
runtime?.log?.(`[tlon] Error fetching channel history: ${error?.message ?? String(error)}`);
|
||||
runtime?.log?.(
|
||||
`[tlon] Error fetching channel history: ${(error as { message?: string })?.message ?? String(error)}`,
|
||||
);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,11 @@ import {
|
||||
isSummarizationRequest,
|
||||
} from "./utils.js";
|
||||
|
||||
function formatError(err: unknown): string {
|
||||
if (err instanceof Error) return err.message;
|
||||
return String(err);
|
||||
}
|
||||
|
||||
export type MonitorTlonOpts = {
|
||||
runtime?: RuntimeEnv;
|
||||
abortSignal?: AbortSignal;
|
||||
@@ -35,6 +40,11 @@ type UrbitMemo = {
|
||||
sent?: number;
|
||||
};
|
||||
|
||||
type UrbitSeal = {
|
||||
"parent-id"?: string;
|
||||
parent?: string;
|
||||
};
|
||||
|
||||
type UrbitUpdate = {
|
||||
id?: string | number;
|
||||
response?: {
|
||||
@@ -42,10 +52,10 @@ type UrbitUpdate = {
|
||||
post?: {
|
||||
id?: string | number;
|
||||
"r-post"?: {
|
||||
set?: { essay?: UrbitMemo };
|
||||
set?: { essay?: UrbitMemo; seal?: UrbitSeal };
|
||||
reply?: {
|
||||
id?: string | number;
|
||||
"r-reply"?: { set?: { memo?: UrbitMemo } };
|
||||
"r-reply"?: { set?: { memo?: UrbitMemo; seal?: UrbitSeal } };
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -113,7 +123,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
runtime.error?.(`[tlon] Failed to authenticate: ${error?.message ?? String(error)}`);
|
||||
runtime.error?.(`[tlon] Failed to authenticate: ${formatError(error)}`);
|
||||
throw error;
|
||||
}
|
||||
|
||||
@@ -127,7 +137,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
groupChannels = discoveredChannels;
|
||||
}
|
||||
} catch (error) {
|
||||
runtime.error?.(`[tlon] Auto-discovery failed: ${error?.message ?? String(error)}`);
|
||||
runtime.error?.(`[tlon] Auto-discovery failed: ${formatError(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +189,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
timestamp: memo.sent || Date.now(),
|
||||
});
|
||||
} catch (error) {
|
||||
runtime.error?.(`[tlon] Error handling DM: ${error?.message ?? String(error)}`);
|
||||
runtime.error?.(`[tlon] Error handling DM: ${formatError(error)}`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -198,6 +208,9 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
}
|
||||
|
||||
const content = memo || essay;
|
||||
if (!content) {
|
||||
return;
|
||||
}
|
||||
const isThreadReply = Boolean(memo);
|
||||
const rawMessageId = isThreadReply ? post?.reply?.id : update?.response?.post?.id;
|
||||
const messageId = rawMessageId != null ? String(rawMessageId) : undefined;
|
||||
@@ -260,7 +273,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
parentId,
|
||||
});
|
||||
} catch (error) {
|
||||
runtime.error?.(`[tlon] Error handling group message: ${error?.message ?? String(error)}`);
|
||||
runtime.error?.(`[tlon] Error handling group message: ${formatError(error)}`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -319,7 +332,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
"3. Action items if any\n" +
|
||||
"4. Notable participants";
|
||||
} catch (error) {
|
||||
const errorMsg = `Sorry, I encountered an error while fetching the channel history: ${error?.message ?? String(error)}`;
|
||||
const errorMsg = `Sorry, I encountered an error while fetching the channel history: ${formatError(error)}`;
|
||||
if (isGroup && groupChannel) {
|
||||
const parsed = parseChannelNest(groupChannel);
|
||||
if (parsed) {
|
||||
@@ -400,10 +413,15 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
const showSignature =
|
||||
account.showModelSignature ?? cfg.channels?.tlon?.showModelSignature ?? false;
|
||||
if (showSignature) {
|
||||
const extPayload = payload as ReplyPayload & {
|
||||
metadata?: { model?: string };
|
||||
model?: string;
|
||||
};
|
||||
const extRoute = route as typeof route & { model?: string };
|
||||
const modelInfo =
|
||||
payload.metadata?.model ||
|
||||
payload.model ||
|
||||
route.model ||
|
||||
extPayload.metadata?.model ||
|
||||
extPayload.model ||
|
||||
extRoute.model ||
|
||||
cfg.agents?.defaults?.model?.primary;
|
||||
replyText = `${replyText}\n\n_[Generated by ${formatModelName(modelInfo)}]_`;
|
||||
}
|
||||
@@ -455,7 +473,9 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
await api!.subscribe({
|
||||
app: "channels",
|
||||
path: `/${channelNest}`,
|
||||
event: handleIncomingGroupMessage(channelNest),
|
||||
event: (data: unknown) => {
|
||||
handleIncomingGroupMessage(channelNest)(data as UrbitUpdate);
|
||||
},
|
||||
err: (error) => {
|
||||
runtime.error?.(`[tlon] Group subscription error for ${channelNest}: ${String(error)}`);
|
||||
},
|
||||
@@ -467,9 +487,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
subscribedChannels.add(channelNest);
|
||||
runtime.log?.(`[tlon] Subscribed to group channel: ${channelNest}`);
|
||||
} catch (error) {
|
||||
runtime.error?.(
|
||||
`[tlon] Failed to subscribe to ${channelNest}: ${error?.message ?? String(error)}`,
|
||||
);
|
||||
runtime.error?.(`[tlon] Failed to subscribe to ${channelNest}: ${formatError(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -481,7 +499,9 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
await api!.subscribe({
|
||||
app: "chat",
|
||||
path: `/dm/${dmShip}`,
|
||||
event: handleIncomingDM,
|
||||
event: (data: unknown) => {
|
||||
handleIncomingDM(data as UrbitUpdate);
|
||||
},
|
||||
err: (error) => {
|
||||
runtime.error?.(`[tlon] DM subscription error for ${dmShip}: ${String(error)}`);
|
||||
},
|
||||
@@ -493,9 +513,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
subscribedDMs.add(dmShip);
|
||||
runtime.log?.(`[tlon] Subscribed to DM with ${dmShip}`);
|
||||
} catch (error) {
|
||||
runtime.error?.(
|
||||
`[tlon] Failed to subscribe to DM with ${dmShip}: ${error?.message ?? String(error)}`,
|
||||
);
|
||||
runtime.error?.(`[tlon] Failed to subscribe to DM with ${dmShip}: ${formatError(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -515,7 +533,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
runtime.error?.(`[tlon] Channel refresh failed: ${error?.message ?? String(error)}`);
|
||||
runtime.error?.(`[tlon] Channel refresh failed: ${formatError(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -530,7 +548,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
runtime.log?.(`[tlon] Found ${dmShips.length} DM conversation(s)`);
|
||||
}
|
||||
} catch (error) {
|
||||
runtime.error?.(`[tlon] Failed to fetch DM list: ${error?.message ?? String(error)}`);
|
||||
runtime.error?.(`[tlon] Failed to fetch DM list: ${formatError(error)}`);
|
||||
}
|
||||
|
||||
for (const dmShip of dmShips) {
|
||||
@@ -549,7 +567,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
() => {
|
||||
if (!opts.abortSignal?.aborted) {
|
||||
refreshChannelSubscriptions().catch((error) => {
|
||||
runtime.error?.(`[tlon] Channel refresh error: ${error?.message ?? String(error)}`);
|
||||
runtime.error?.(`[tlon] Channel refresh error: ${formatError(error)}`);
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -557,8 +575,9 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
);
|
||||
|
||||
if (opts.abortSignal) {
|
||||
const signal = opts.abortSignal;
|
||||
await new Promise((resolve) => {
|
||||
opts.abortSignal.addEventListener(
|
||||
signal.addEventListener(
|
||||
"abort",
|
||||
() => {
|
||||
clearInterval(pollInterval);
|
||||
@@ -574,7 +593,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
try {
|
||||
await api?.close();
|
||||
} catch (error) {
|
||||
runtime.error?.(`[tlon] Cleanup error: ${error?.message ?? String(error)}`);
|
||||
runtime.error?.(`[tlon] Cleanup error: ${formatError(error)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ export async function sendGroupMessage({
|
||||
let formattedReplyId = replyToId;
|
||||
if (replyToId && /^\d+$/.test(replyToId)) {
|
||||
try {
|
||||
formattedReplyId = formatUd(BigInt(replyToId));
|
||||
formattedReplyId = scot("ud", BigInt(replyToId));
|
||||
} catch {
|
||||
// Fall back to raw ID if formatting fails
|
||||
}
|
||||
|
||||
@@ -204,7 +204,8 @@ export class UrbitSSEClient {
|
||||
if (!body) {
|
||||
return;
|
||||
}
|
||||
const stream = body instanceof ReadableStream ? Readable.fromWeb(body) : body;
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
const stream = body instanceof ReadableStream ? Readable.fromWeb(body as any) : body;
|
||||
let buffer = "";
|
||||
|
||||
try {
|
||||
|
||||
@@ -15,7 +15,7 @@ function errorResponse(error: string) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
type: "text" as const,
|
||||
text: JSON.stringify({ ok: false, error }),
|
||||
},
|
||||
],
|
||||
@@ -120,11 +120,12 @@ export const twitchMessageActions: ChannelMessageActionAdapter = {
|
||||
* accountId: "default",
|
||||
* });
|
||||
*/
|
||||
handleAction: async (
|
||||
ctx: ChannelMessageActionContext,
|
||||
): Promise<{ content: Array<{ type: string; text: string }> } | null> => {
|
||||
handleAction: async (ctx: ChannelMessageActionContext) => {
|
||||
if (ctx.action !== "send") {
|
||||
return null;
|
||||
return {
|
||||
content: [{ type: "text" as const, text: "Unsupported action" }],
|
||||
details: { ok: false, error: "Unsupported action" },
|
||||
};
|
||||
}
|
||||
|
||||
const message = readStringParam(ctx.params, "message", { required: true });
|
||||
@@ -159,7 +160,7 @@ export const twitchMessageActions: ChannelMessageActionAdapter = {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
type: "text" as const,
|
||||
text: JSON.stringify(result),
|
||||
},
|
||||
],
|
||||
|
||||
@@ -104,7 +104,8 @@ export const twitchOutbound: ChannelOutboundAdapter = {
|
||||
* });
|
||||
*/
|
||||
sendText: async (params: ChannelOutboundContext): Promise<OutboundDeliveryResult> => {
|
||||
const { cfg, to, text, accountId, signal } = params;
|
||||
const { cfg, to, text, accountId } = params;
|
||||
const signal = (params as { signal?: AbortSignal }).signal;
|
||||
|
||||
if (signal?.aborted) {
|
||||
throw new Error("Outbound delivery aborted");
|
||||
@@ -142,7 +143,6 @@ export const twitchOutbound: ChannelOutboundAdapter = {
|
||||
channel: "twitch",
|
||||
messageId: result.messageId,
|
||||
timestamp: Date.now(),
|
||||
to: normalizeTwitchChannel(channel),
|
||||
};
|
||||
},
|
||||
|
||||
@@ -165,7 +165,8 @@ export const twitchOutbound: ChannelOutboundAdapter = {
|
||||
* });
|
||||
*/
|
||||
sendMedia: async (params: ChannelOutboundContext): Promise<OutboundDeliveryResult> => {
|
||||
const { text, mediaUrl, signal } = params;
|
||||
const { text, mediaUrl } = params;
|
||||
const signal = (params as { signal?: AbortSignal }).signal;
|
||||
|
||||
if (signal?.aborted) {
|
||||
throw new Error("Outbound delivery aborted");
|
||||
|
||||
@@ -27,16 +27,16 @@ export async function probeTwitch(
|
||||
): Promise<ProbeTwitchResult> {
|
||||
const started = Date.now();
|
||||
|
||||
if (!account.token || !account.username) {
|
||||
if (!account.accessToken || !account.username) {
|
||||
return {
|
||||
ok: false,
|
||||
error: "missing credentials (token, username)",
|
||||
error: "missing credentials (accessToken, username)",
|
||||
username: account.username,
|
||||
elapsedMs: Date.now() - started,
|
||||
};
|
||||
}
|
||||
|
||||
const rawToken = normalizeToken(account.token.trim());
|
||||
const rawToken = normalizeToken(account.accessToken.trim());
|
||||
|
||||
let client: ChatClient | undefined;
|
||||
|
||||
|
||||
@@ -51,8 +51,8 @@ export async function resolveTwitchTargets(
|
||||
): Promise<ChannelResolveResult[]> {
|
||||
const log = createLogger(logger);
|
||||
|
||||
if (!account.clientId || !account.token) {
|
||||
log.error("Missing Twitch client ID or token");
|
||||
if (!account.clientId || !account.accessToken) {
|
||||
log.error("Missing Twitch client ID or accessToken");
|
||||
return inputs.map((input) => ({
|
||||
input,
|
||||
resolved: false,
|
||||
@@ -60,7 +60,7 @@ export async function resolveTwitchTargets(
|
||||
}));
|
||||
}
|
||||
|
||||
const normalizedToken = normalizeToken(account.token);
|
||||
const normalizedToken = normalizeToken(account.accessToken);
|
||||
|
||||
const authProvider = new StaticAuthProvider(account.clientId, normalizedToken);
|
||||
const apiClient = new ApiClient({ authProvider });
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
* Detects and reports configuration issues for Twitch accounts.
|
||||
*/
|
||||
|
||||
import type { ChannelAccountSnapshot, ChannelStatusIssue } from "./types.js";
|
||||
import type { ChannelStatusIssue } from "openclaw/plugin-sdk";
|
||||
import type { ChannelAccountSnapshot } from "./types.js";
|
||||
import { getAccountConfig } from "./config.js";
|
||||
import { resolveTwitchToken } from "./token.js";
|
||||
import { isAccountConfigured } from "./utils/twitch.js";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { GatewayRequestHandlerOptions, OpenClawPluginApi } from "openclaw/plugin-sdk";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import type { CoreConfig } from "./src/core-bridge.js";
|
||||
import { registerVoiceCallCli } from "./src/cli.js";
|
||||
@@ -144,7 +145,7 @@ const voiceCallPlugin = {
|
||||
name: "Voice Call",
|
||||
description: "Voice-call plugin with Telnyx/Twilio/Plivo providers",
|
||||
configSchema: voiceCallConfigSchema,
|
||||
register(api) {
|
||||
register(api: OpenClawPluginApi) {
|
||||
const config = resolveVoiceCallConfig(voiceCallConfigSchema.parse(api.pluginConfig));
|
||||
const validation = validateProviderConfig(config);
|
||||
|
||||
@@ -188,142 +189,160 @@ const voiceCallPlugin = {
|
||||
respond(false, { error: err instanceof Error ? err.message : String(err) });
|
||||
};
|
||||
|
||||
api.registerGatewayMethod("voicecall.initiate", async ({ params, respond }) => {
|
||||
try {
|
||||
const message = typeof params?.message === "string" ? params.message.trim() : "";
|
||||
if (!message) {
|
||||
respond(false, { error: "message required" });
|
||||
return;
|
||||
api.registerGatewayMethod(
|
||||
"voicecall.initiate",
|
||||
async ({ params, respond }: GatewayRequestHandlerOptions) => {
|
||||
try {
|
||||
const message = typeof params?.message === "string" ? params.message.trim() : "";
|
||||
if (!message) {
|
||||
respond(false, { error: "message required" });
|
||||
return;
|
||||
}
|
||||
const rt = await ensureRuntime();
|
||||
const to =
|
||||
typeof params?.to === "string" && params.to.trim()
|
||||
? params.to.trim()
|
||||
: rt.config.toNumber;
|
||||
if (!to) {
|
||||
respond(false, { error: "to required" });
|
||||
return;
|
||||
}
|
||||
const mode =
|
||||
params?.mode === "notify" || params?.mode === "conversation" ? params.mode : undefined;
|
||||
const result = await rt.manager.initiateCall(to, undefined, {
|
||||
message,
|
||||
mode,
|
||||
});
|
||||
if (!result.success) {
|
||||
respond(false, { error: result.error || "initiate failed" });
|
||||
return;
|
||||
}
|
||||
respond(true, { callId: result.callId, initiated: true });
|
||||
} catch (err) {
|
||||
sendError(respond, err);
|
||||
}
|
||||
const rt = await ensureRuntime();
|
||||
const to =
|
||||
typeof params?.to === "string" && params.to.trim()
|
||||
? params.to.trim()
|
||||
: rt.config.toNumber;
|
||||
if (!to) {
|
||||
respond(false, { error: "to required" });
|
||||
return;
|
||||
}
|
||||
const mode =
|
||||
params?.mode === "notify" || params?.mode === "conversation" ? params.mode : undefined;
|
||||
const result = await rt.manager.initiateCall(to, undefined, {
|
||||
message,
|
||||
mode,
|
||||
});
|
||||
if (!result.success) {
|
||||
respond(false, { error: result.error || "initiate failed" });
|
||||
return;
|
||||
}
|
||||
respond(true, { callId: result.callId, initiated: true });
|
||||
} catch (err) {
|
||||
sendError(respond, err);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
api.registerGatewayMethod("voicecall.continue", async ({ params, respond }) => {
|
||||
try {
|
||||
const callId = typeof params?.callId === "string" ? params.callId.trim() : "";
|
||||
const message = typeof params?.message === "string" ? params.message.trim() : "";
|
||||
if (!callId || !message) {
|
||||
respond(false, { error: "callId and message required" });
|
||||
return;
|
||||
api.registerGatewayMethod(
|
||||
"voicecall.continue",
|
||||
async ({ params, respond }: GatewayRequestHandlerOptions) => {
|
||||
try {
|
||||
const callId = typeof params?.callId === "string" ? params.callId.trim() : "";
|
||||
const message = typeof params?.message === "string" ? params.message.trim() : "";
|
||||
if (!callId || !message) {
|
||||
respond(false, { error: "callId and message required" });
|
||||
return;
|
||||
}
|
||||
const rt = await ensureRuntime();
|
||||
const result = await rt.manager.continueCall(callId, message);
|
||||
if (!result.success) {
|
||||
respond(false, { error: result.error || "continue failed" });
|
||||
return;
|
||||
}
|
||||
respond(true, { success: true, transcript: result.transcript });
|
||||
} catch (err) {
|
||||
sendError(respond, err);
|
||||
}
|
||||
const rt = await ensureRuntime();
|
||||
const result = await rt.manager.continueCall(callId, message);
|
||||
if (!result.success) {
|
||||
respond(false, { error: result.error || "continue failed" });
|
||||
return;
|
||||
}
|
||||
respond(true, { success: true, transcript: result.transcript });
|
||||
} catch (err) {
|
||||
sendError(respond, err);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
api.registerGatewayMethod("voicecall.speak", async ({ params, respond }) => {
|
||||
try {
|
||||
const callId = typeof params?.callId === "string" ? params.callId.trim() : "";
|
||||
const message = typeof params?.message === "string" ? params.message.trim() : "";
|
||||
if (!callId || !message) {
|
||||
respond(false, { error: "callId and message required" });
|
||||
return;
|
||||
api.registerGatewayMethod(
|
||||
"voicecall.speak",
|
||||
async ({ params, respond }: GatewayRequestHandlerOptions) => {
|
||||
try {
|
||||
const callId = typeof params?.callId === "string" ? params.callId.trim() : "";
|
||||
const message = typeof params?.message === "string" ? params.message.trim() : "";
|
||||
if (!callId || !message) {
|
||||
respond(false, { error: "callId and message required" });
|
||||
return;
|
||||
}
|
||||
const rt = await ensureRuntime();
|
||||
const result = await rt.manager.speak(callId, message);
|
||||
if (!result.success) {
|
||||
respond(false, { error: result.error || "speak failed" });
|
||||
return;
|
||||
}
|
||||
respond(true, { success: true });
|
||||
} catch (err) {
|
||||
sendError(respond, err);
|
||||
}
|
||||
const rt = await ensureRuntime();
|
||||
const result = await rt.manager.speak(callId, message);
|
||||
if (!result.success) {
|
||||
respond(false, { error: result.error || "speak failed" });
|
||||
return;
|
||||
}
|
||||
respond(true, { success: true });
|
||||
} catch (err) {
|
||||
sendError(respond, err);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
api.registerGatewayMethod("voicecall.end", async ({ params, respond }) => {
|
||||
try {
|
||||
const callId = typeof params?.callId === "string" ? params.callId.trim() : "";
|
||||
if (!callId) {
|
||||
respond(false, { error: "callId required" });
|
||||
return;
|
||||
api.registerGatewayMethod(
|
||||
"voicecall.end",
|
||||
async ({ params, respond }: GatewayRequestHandlerOptions) => {
|
||||
try {
|
||||
const callId = typeof params?.callId === "string" ? params.callId.trim() : "";
|
||||
if (!callId) {
|
||||
respond(false, { error: "callId required" });
|
||||
return;
|
||||
}
|
||||
const rt = await ensureRuntime();
|
||||
const result = await rt.manager.endCall(callId);
|
||||
if (!result.success) {
|
||||
respond(false, { error: result.error || "end failed" });
|
||||
return;
|
||||
}
|
||||
respond(true, { success: true });
|
||||
} catch (err) {
|
||||
sendError(respond, err);
|
||||
}
|
||||
const rt = await ensureRuntime();
|
||||
const result = await rt.manager.endCall(callId);
|
||||
if (!result.success) {
|
||||
respond(false, { error: result.error || "end failed" });
|
||||
return;
|
||||
}
|
||||
respond(true, { success: true });
|
||||
} catch (err) {
|
||||
sendError(respond, err);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
api.registerGatewayMethod("voicecall.status", async ({ params, respond }) => {
|
||||
try {
|
||||
const raw =
|
||||
typeof params?.callId === "string"
|
||||
? params.callId.trim()
|
||||
: typeof params?.sid === "string"
|
||||
? params.sid.trim()
|
||||
: "";
|
||||
if (!raw) {
|
||||
respond(false, { error: "callId required" });
|
||||
return;
|
||||
api.registerGatewayMethod(
|
||||
"voicecall.status",
|
||||
async ({ params, respond }: GatewayRequestHandlerOptions) => {
|
||||
try {
|
||||
const raw =
|
||||
typeof params?.callId === "string"
|
||||
? params.callId.trim()
|
||||
: typeof params?.sid === "string"
|
||||
? params.sid.trim()
|
||||
: "";
|
||||
if (!raw) {
|
||||
respond(false, { error: "callId required" });
|
||||
return;
|
||||
}
|
||||
const rt = await ensureRuntime();
|
||||
const call = rt.manager.getCall(raw) || rt.manager.getCallByProviderCallId(raw);
|
||||
if (!call) {
|
||||
respond(true, { found: false });
|
||||
return;
|
||||
}
|
||||
respond(true, { found: true, call });
|
||||
} catch (err) {
|
||||
sendError(respond, err);
|
||||
}
|
||||
const rt = await ensureRuntime();
|
||||
const call = rt.manager.getCall(raw) || rt.manager.getCallByProviderCallId(raw);
|
||||
if (!call) {
|
||||
respond(true, { found: false });
|
||||
return;
|
||||
}
|
||||
respond(true, { found: true, call });
|
||||
} catch (err) {
|
||||
sendError(respond, err);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
api.registerGatewayMethod("voicecall.start", async ({ params, respond }) => {
|
||||
try {
|
||||
const to = typeof params?.to === "string" ? params.to.trim() : "";
|
||||
const message = typeof params?.message === "string" ? params.message.trim() : "";
|
||||
if (!to) {
|
||||
respond(false, { error: "to required" });
|
||||
return;
|
||||
api.registerGatewayMethod(
|
||||
"voicecall.start",
|
||||
async ({ params, respond }: GatewayRequestHandlerOptions) => {
|
||||
try {
|
||||
const to = typeof params?.to === "string" ? params.to.trim() : "";
|
||||
const message = typeof params?.message === "string" ? params.message.trim() : "";
|
||||
if (!to) {
|
||||
respond(false, { error: "to required" });
|
||||
return;
|
||||
}
|
||||
const rt = await ensureRuntime();
|
||||
const result = await rt.manager.initiateCall(to, undefined, {
|
||||
message: message || undefined,
|
||||
});
|
||||
if (!result.success) {
|
||||
respond(false, { error: result.error || "initiate failed" });
|
||||
return;
|
||||
}
|
||||
respond(true, { callId: result.callId, initiated: true });
|
||||
} catch (err) {
|
||||
sendError(respond, err);
|
||||
}
|
||||
const rt = await ensureRuntime();
|
||||
const result = await rt.manager.initiateCall(to, undefined, {
|
||||
message: message || undefined,
|
||||
});
|
||||
if (!result.success) {
|
||||
respond(false, { error: result.error || "initiate failed" });
|
||||
return;
|
||||
}
|
||||
respond(true, { callId: result.callId, initiated: true });
|
||||
} catch (err) {
|
||||
sendError(respond, err);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
api.registerTool({
|
||||
name: "voice_call",
|
||||
@@ -332,7 +351,7 @@ const voiceCallPlugin = {
|
||||
parameters: VoiceCallToolSchema,
|
||||
async execute(_toolCallId, params) {
|
||||
const json = (payload: unknown) => ({
|
||||
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
||||
content: [{ type: "text" as const, text: JSON.stringify(payload, null, 2) }],
|
||||
details: payload,
|
||||
});
|
||||
|
||||
|
||||
@@ -146,7 +146,7 @@ export async function generateVoiceResponse(
|
||||
|
||||
const text = texts.join(" ") || null;
|
||||
|
||||
if (!text && result.meta.aborted) {
|
||||
if (!text && result.meta?.aborted) {
|
||||
return { text: null, error: "Response generation was aborted" };
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ type Logger = {
|
||||
info: (message: string) => void;
|
||||
warn: (message: string) => void;
|
||||
error: (message: string) => void;
|
||||
debug: (message: string) => void;
|
||||
debug?: (message: string) => void;
|
||||
};
|
||||
|
||||
function isLoopbackBind(bind: string | undefined): boolean {
|
||||
|
||||
@@ -3,6 +3,8 @@ import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk";
|
||||
import type { ResolvedZaloAccount, ZaloAccountConfig, ZaloConfig } from "./types.js";
|
||||
import { resolveZaloToken } from "./token.js";
|
||||
|
||||
export type { ResolvedZaloAccount };
|
||||
|
||||
function listConfiguredAccountIds(cfg: OpenClawConfig): string[] {
|
||||
const accounts = (cfg.channels?.zalo as ZaloConfig | undefined)?.accounts;
|
||||
if (!accounts || typeof accounts !== "object") {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Dispatcher } from "undici";
|
||||
import type { Dispatcher, RequestInit as UndiciRequestInit } from "undici";
|
||||
import { ProxyAgent, fetch as undiciFetch } from "undici";
|
||||
import type { ZaloFetch } from "./api.js";
|
||||
|
||||
@@ -15,7 +15,10 @@ export function resolveZaloProxyFetch(proxyUrl?: string | null): ZaloFetch | und
|
||||
}
|
||||
const agent = new ProxyAgent(trimmed);
|
||||
const fetcher: ZaloFetch = (input, init) =>
|
||||
undiciFetch(input, { ...init, dispatcher: agent as Dispatcher });
|
||||
undiciFetch(input, {
|
||||
...init,
|
||||
dispatcher: agent,
|
||||
} as UndiciRequestInit) as unknown as Promise<Response>;
|
||||
proxyCache.set(trimmed, fetcher);
|
||||
return fetcher;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
||||
import type { AnyAgentTool, OpenClawPluginApi } from "openclaw/plugin-sdk";
|
||||
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
|
||||
import { zalouserDock, zalouserPlugin } from "./src/channel.js";
|
||||
import { setZalouserRuntime } from "./src/runtime.js";
|
||||
@@ -24,7 +24,7 @@ const plugin = {
|
||||
"friends (list/search friends), groups (list groups), me (profile info), status (auth check).",
|
||||
parameters: ZalouserToolSchema,
|
||||
execute: executeZalouserTool,
|
||||
});
|
||||
} as AnyAgentTool);
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -625,7 +625,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount> = {
|
||||
}
|
||||
ctx.setStatus({
|
||||
accountId: account.accountId,
|
||||
user: userInfo,
|
||||
profile: userInfo,
|
||||
});
|
||||
} catch {
|
||||
// ignore probe errors
|
||||
|
||||
@@ -3,6 +3,11 @@ import { runZca, parseJsonOutput } from "./zca.js";
|
||||
|
||||
const ACTIONS = ["send", "image", "link", "friends", "groups", "me", "status"] as const;
|
||||
|
||||
type AgentToolResult = {
|
||||
content: Array<{ type: string; text: string }>;
|
||||
details?: unknown;
|
||||
};
|
||||
|
||||
function stringEnum<T extends readonly string[]>(
|
||||
values: T,
|
||||
options: { description?: string } = {},
|
||||
@@ -38,12 +43,7 @@ type ToolParams = {
|
||||
url?: string;
|
||||
};
|
||||
|
||||
type ToolResult = {
|
||||
content: Array<{ type: string; text: string }>;
|
||||
details: unknown;
|
||||
};
|
||||
|
||||
function json(payload: unknown): ToolResult {
|
||||
function json(payload: unknown): AgentToolResult {
|
||||
return {
|
||||
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
||||
details: payload,
|
||||
@@ -53,7 +53,9 @@ function json(payload: unknown): ToolResult {
|
||||
export async function executeZalouserTool(
|
||||
_toolCallId: string,
|
||||
params: ToolParams,
|
||||
): Promise<ToolResult> {
|
||||
_signal?: AbortSignal,
|
||||
_onUpdate?: unknown,
|
||||
): Promise<AgentToolResult> {
|
||||
try {
|
||||
switch (params.action) {
|
||||
case "send": {
|
||||
|
||||
@@ -125,6 +125,7 @@ export type ChannelAccountSnapshot = {
|
||||
botTokenSource?: string;
|
||||
appTokenSource?: string;
|
||||
credentialSource?: string;
|
||||
secretSource?: string;
|
||||
audienceType?: string;
|
||||
audience?: string;
|
||||
webhookPath?: string;
|
||||
@@ -139,6 +140,10 @@ export type ChannelAccountSnapshot = {
|
||||
audit?: unknown;
|
||||
application?: unknown;
|
||||
bot?: unknown;
|
||||
publicKey?: string | null;
|
||||
profile?: unknown;
|
||||
channelAccessToken?: string;
|
||||
channelSecret?: string;
|
||||
};
|
||||
|
||||
export type ChannelLogSink = {
|
||||
@@ -328,4 +333,5 @@ export type ChannelPollContext = {
|
||||
to: string;
|
||||
poll: PollInput;
|
||||
accountId?: string | null;
|
||||
threadId?: string | null;
|
||||
};
|
||||
|
||||
@@ -23,6 +23,19 @@ export type ChannelDefaultsConfig = {
|
||||
heartbeat?: ChannelHeartbeatVisibilityConfig;
|
||||
};
|
||||
|
||||
/**
|
||||
* Base type for extension channel config sections.
|
||||
* Extensions can use this as a starting point for their channel config.
|
||||
*/
|
||||
export type ExtensionChannelConfig = {
|
||||
enabled?: boolean;
|
||||
allowFrom?: string | string[];
|
||||
dmPolicy?: string;
|
||||
groupPolicy?: GroupPolicy;
|
||||
accounts?: Record<string, unknown>;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
export type ChannelsConfig = {
|
||||
defaults?: ChannelDefaultsConfig;
|
||||
whatsapp?: WhatsAppConfig;
|
||||
@@ -33,5 +46,7 @@ export type ChannelsConfig = {
|
||||
signal?: SignalConfig;
|
||||
imessage?: IMessageConfig;
|
||||
msteams?: MSTeamsConfig;
|
||||
[key: string]: unknown;
|
||||
// Extension channels use dynamic keys - use ExtensionChannelConfig in extensions
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
@@ -59,20 +59,25 @@ export type {
|
||||
} from "../channels/plugins/types.js";
|
||||
export type { ChannelConfigSchema, ChannelPlugin } from "../channels/plugins/types.plugin.js";
|
||||
export type {
|
||||
AnyAgentTool,
|
||||
OpenClawPluginApi,
|
||||
OpenClawPluginService,
|
||||
OpenClawPluginServiceContext,
|
||||
ProviderAuthContext,
|
||||
ProviderAuthResult,
|
||||
} from "../plugins/types.js";
|
||||
export type {
|
||||
GatewayRequestHandler,
|
||||
GatewayRequestHandlerOptions,
|
||||
RespondFn,
|
||||
} from "../gateway/server-methods/types.js";
|
||||
export type { PluginRuntime } from "../plugins/runtime/types.js";
|
||||
export type { PluginRuntime, RuntimeLogger } from "../plugins/runtime/types.js";
|
||||
export { normalizePluginHttpPath } from "../plugins/http-path.js";
|
||||
export { registerPluginHttpRoute } from "../plugins/http-registry.js";
|
||||
export { emptyPluginConfigSchema } from "../plugins/config-schema.js";
|
||||
export type { OpenClawConfig } from "../config/config.js";
|
||||
/** @deprecated Use OpenClawConfig instead */
|
||||
export type { OpenClawConfig as ClawdbotConfig } from "../config/config.js";
|
||||
export type { ChannelDock } from "../channels/dock.js";
|
||||
export { getChatChannelMeta } from "../channels/registry.js";
|
||||
export type {
|
||||
@@ -130,6 +135,7 @@ export {
|
||||
listDevicePairing,
|
||||
rejectDevicePairing,
|
||||
} from "../infra/device-pairing.js";
|
||||
export { formatErrorMessage } from "../infra/errors.js";
|
||||
export { resolveToolsBySender } from "../config/group-policy.js";
|
||||
export {
|
||||
buildPendingHistoryContextFromMap,
|
||||
|
||||
@@ -169,10 +169,10 @@ type BuildTemplateMessageFromPayload =
|
||||
type MonitorLineProvider = typeof import("../../line/monitor.js").monitorLineProvider;
|
||||
|
||||
export type RuntimeLogger = {
|
||||
debug?: (message: string) => void;
|
||||
info: (message: string) => void;
|
||||
warn: (message: string) => void;
|
||||
error: (message: string) => void;
|
||||
debug?: (message: string, meta?: Record<string, unknown>) => void;
|
||||
info: (message: string, meta?: Record<string, unknown>) => void;
|
||||
warn: (message: string, meta?: Record<string, unknown>) => void;
|
||||
error: (message: string, meta?: Record<string, unknown>) => void;
|
||||
};
|
||||
|
||||
export type PluginRuntime = {
|
||||
|
||||
@@ -17,6 +17,7 @@ import type { WizardPrompter } from "../wizard/prompts.js";
|
||||
import type { PluginRuntime } from "./runtime/types.js";
|
||||
|
||||
export type { PluginRuntime } from "./runtime/types.js";
|
||||
export type { AnyAgentTool } from "../agents/tools/common.js";
|
||||
|
||||
export type PluginLogger = {
|
||||
debug?: (message: string) => void;
|
||||
|
||||
@@ -13,7 +13,7 @@ import type { OutboundSendDeps } from "../src/infra/outbound/deliver.js";
|
||||
import { installProcessWarningFilter } from "../src/infra/warning-filter.js";
|
||||
import { setActivePluginRegistry } from "../src/plugins/runtime.js";
|
||||
import { createTestRegistry } from "../src/test-utils/channel-plugins.js";
|
||||
import { withIsolatedTestHome } from "./test-env";
|
||||
import { withIsolatedTestHome } from "./test-env.js";
|
||||
|
||||
installProcessWarningFilter();
|
||||
|
||||
@@ -46,7 +46,8 @@ const createStubOutbound = (
|
||||
sendText: async ({ deps, to, text }) => {
|
||||
const send = pickSendFn(id, deps);
|
||||
if (send) {
|
||||
const result = await send(to, text, {});
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
const result = await send(to, text, { verbose: false } as any);
|
||||
return { channel: id, ...result };
|
||||
}
|
||||
return { channel: id, messageId: "test" };
|
||||
@@ -54,7 +55,8 @@ const createStubOutbound = (
|
||||
sendMedia: async ({ deps, to, text, mediaUrl }) => {
|
||||
const send = pickSendFn(id, deps);
|
||||
if (send) {
|
||||
const result = await send(to, text, { mediaUrl });
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
const result = await send(to, text, { verbose: false, mediaUrl } as any);
|
||||
return { channel: id, ...result };
|
||||
}
|
||||
return { channel: id, messageId: "test" };
|
||||
@@ -90,14 +92,14 @@ const createStubPlugin = (params: {
|
||||
const ids = accounts ? Object.keys(accounts).filter(Boolean) : [];
|
||||
return ids.length > 0 ? ids : ["default"];
|
||||
},
|
||||
resolveAccount: (cfg: OpenClawConfig, accountId: string) => {
|
||||
resolveAccount: (cfg: OpenClawConfig, accountId?: string | null) => {
|
||||
const channels = cfg.channels as Record<string, unknown> | undefined;
|
||||
const entry = channels?.[params.id];
|
||||
if (!entry || typeof entry !== "object") {
|
||||
return {};
|
||||
}
|
||||
const accounts = (entry as { accounts?: Record<string, unknown> }).accounts;
|
||||
const match = accounts?.[accountId];
|
||||
const match = accountId ? accounts?.[accountId] : undefined;
|
||||
return (match && typeof match === "object") || typeof match === "string" ? match : entry;
|
||||
},
|
||||
isConfigured: async (_account, cfg: OpenClawConfig) => {
|
||||
|
||||
@@ -16,8 +16,12 @@
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"target": "es2023",
|
||||
"useDefineForClassFields": false
|
||||
"useDefineForClassFields": false,
|
||||
"paths": {
|
||||
"*": ["./*"],
|
||||
"openclaw/plugin-sdk": ["./src/plugin-sdk/index.ts"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*", "ui/**/*"],
|
||||
"exclude": ["node_modules", "dist", "src/**/*.test.ts"]
|
||||
"include": ["src/**/*", "ui/**/*", "extensions/**/*"],
|
||||
"exclude": ["node_modules", "dist", "src/**/*.test.ts", "extensions/**/*.test.ts"]
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
1911
ui/src/ui/views/usageStyles.ts
Normal file
1911
ui/src/ui/views/usageStyles.ts
Normal file
File diff suppressed because it is too large
Load Diff
285
ui/src/ui/views/usageTypes.ts
Normal file
285
ui/src/ui/views/usageTypes.ts
Normal file
@@ -0,0 +1,285 @@
|
||||
export type UsageSessionEntry = {
|
||||
key: string;
|
||||
label?: string;
|
||||
sessionId?: string;
|
||||
updatedAt?: number;
|
||||
agentId?: string;
|
||||
channel?: string;
|
||||
chatType?: string;
|
||||
origin?: {
|
||||
label?: string;
|
||||
provider?: string;
|
||||
surface?: string;
|
||||
chatType?: string;
|
||||
from?: string;
|
||||
to?: string;
|
||||
accountId?: string;
|
||||
threadId?: string | number;
|
||||
};
|
||||
modelOverride?: string;
|
||||
providerOverride?: string;
|
||||
modelProvider?: string;
|
||||
model?: string;
|
||||
usage: {
|
||||
input: number;
|
||||
output: number;
|
||||
cacheRead: number;
|
||||
cacheWrite: number;
|
||||
totalTokens: number;
|
||||
totalCost: number;
|
||||
inputCost?: number;
|
||||
outputCost?: number;
|
||||
cacheReadCost?: number;
|
||||
cacheWriteCost?: number;
|
||||
missingCostEntries: number;
|
||||
firstActivity?: number;
|
||||
lastActivity?: number;
|
||||
durationMs?: number;
|
||||
activityDates?: string[]; // YYYY-MM-DD dates when session had activity
|
||||
dailyBreakdown?: Array<{ date: string; tokens: number; cost: number }>; // Per-day breakdown
|
||||
dailyMessageCounts?: Array<{
|
||||
date: string;
|
||||
total: number;
|
||||
user: number;
|
||||
assistant: number;
|
||||
toolCalls: number;
|
||||
toolResults: number;
|
||||
errors: number;
|
||||
}>;
|
||||
dailyLatency?: Array<{
|
||||
date: string;
|
||||
count: number;
|
||||
avgMs: number;
|
||||
p95Ms: number;
|
||||
minMs: number;
|
||||
maxMs: number;
|
||||
}>;
|
||||
dailyModelUsage?: Array<{
|
||||
date: string;
|
||||
provider?: string;
|
||||
model?: string;
|
||||
tokens: number;
|
||||
cost: number;
|
||||
count: number;
|
||||
}>;
|
||||
messageCounts?: {
|
||||
total: number;
|
||||
user: number;
|
||||
assistant: number;
|
||||
toolCalls: number;
|
||||
toolResults: number;
|
||||
errors: number;
|
||||
};
|
||||
toolUsage?: {
|
||||
totalCalls: number;
|
||||
uniqueTools: number;
|
||||
tools: Array<{ name: string; count: number }>;
|
||||
};
|
||||
modelUsage?: Array<{
|
||||
provider?: string;
|
||||
model?: string;
|
||||
count: number;
|
||||
totals: UsageTotals;
|
||||
}>;
|
||||
latency?: {
|
||||
count: number;
|
||||
avgMs: number;
|
||||
p95Ms: number;
|
||||
minMs: number;
|
||||
maxMs: number;
|
||||
};
|
||||
} | null;
|
||||
contextWeight?: {
|
||||
systemPrompt: { chars: number; projectContextChars: number; nonProjectContextChars: number };
|
||||
skills: { promptChars: number; entries: Array<{ name: string; blockChars: number }> };
|
||||
tools: {
|
||||
listChars: number;
|
||||
schemaChars: number;
|
||||
entries: Array<{ name: string; summaryChars: number; schemaChars: number }>;
|
||||
};
|
||||
injectedWorkspaceFiles: Array<{
|
||||
name: string;
|
||||
path: string;
|
||||
rawChars: number;
|
||||
injectedChars: number;
|
||||
truncated: boolean;
|
||||
}>;
|
||||
} | null;
|
||||
};
|
||||
|
||||
export type UsageTotals = {
|
||||
input: number;
|
||||
output: number;
|
||||
cacheRead: number;
|
||||
cacheWrite: number;
|
||||
totalTokens: number;
|
||||
totalCost: number;
|
||||
inputCost: number;
|
||||
outputCost: number;
|
||||
cacheReadCost: number;
|
||||
cacheWriteCost: number;
|
||||
missingCostEntries: number;
|
||||
};
|
||||
|
||||
export type CostDailyEntry = UsageTotals & { date: string };
|
||||
|
||||
export type UsageAggregates = {
|
||||
messages: {
|
||||
total: number;
|
||||
user: number;
|
||||
assistant: number;
|
||||
toolCalls: number;
|
||||
toolResults: number;
|
||||
errors: number;
|
||||
};
|
||||
tools: {
|
||||
totalCalls: number;
|
||||
uniqueTools: number;
|
||||
tools: Array<{ name: string; count: number }>;
|
||||
};
|
||||
byModel: Array<{
|
||||
provider?: string;
|
||||
model?: string;
|
||||
count: number;
|
||||
totals: UsageTotals;
|
||||
}>;
|
||||
byProvider: Array<{
|
||||
provider?: string;
|
||||
model?: string;
|
||||
count: number;
|
||||
totals: UsageTotals;
|
||||
}>;
|
||||
byAgent: Array<{ agentId: string; totals: UsageTotals }>;
|
||||
byChannel: Array<{ channel: string; totals: UsageTotals }>;
|
||||
latency?: {
|
||||
count: number;
|
||||
avgMs: number;
|
||||
p95Ms: number;
|
||||
minMs: number;
|
||||
maxMs: number;
|
||||
};
|
||||
dailyLatency?: Array<{
|
||||
date: string;
|
||||
count: number;
|
||||
avgMs: number;
|
||||
p95Ms: number;
|
||||
minMs: number;
|
||||
maxMs: number;
|
||||
}>;
|
||||
modelDaily?: Array<{
|
||||
date: string;
|
||||
provider?: string;
|
||||
model?: string;
|
||||
tokens: number;
|
||||
cost: number;
|
||||
count: number;
|
||||
}>;
|
||||
daily: Array<{
|
||||
date: string;
|
||||
tokens: number;
|
||||
cost: number;
|
||||
messages: number;
|
||||
toolCalls: number;
|
||||
errors: number;
|
||||
}>;
|
||||
};
|
||||
|
||||
export type UsageColumnId =
|
||||
| "channel"
|
||||
| "agent"
|
||||
| "provider"
|
||||
| "model"
|
||||
| "messages"
|
||||
| "tools"
|
||||
| "errors"
|
||||
| "duration";
|
||||
|
||||
export type TimeSeriesPoint = {
|
||||
timestamp: number;
|
||||
input: number;
|
||||
output: number;
|
||||
cacheRead: number;
|
||||
cacheWrite: number;
|
||||
totalTokens: number;
|
||||
cost: number;
|
||||
cumulativeTokens: number;
|
||||
cumulativeCost: number;
|
||||
};
|
||||
|
||||
export type UsageProps = {
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
sessions: UsageSessionEntry[];
|
||||
sessionsLimitReached: boolean; // True if 1000 session cap was hit
|
||||
totals: UsageTotals | null;
|
||||
aggregates: UsageAggregates | null;
|
||||
costDaily: CostDailyEntry[];
|
||||
selectedSessions: string[]; // Support multiple session selection
|
||||
selectedDays: string[]; // Support multiple day selection
|
||||
selectedHours: number[]; // Support multiple hour selection
|
||||
chartMode: "tokens" | "cost";
|
||||
dailyChartMode: "total" | "by-type";
|
||||
timeSeriesMode: "cumulative" | "per-turn";
|
||||
timeSeriesBreakdownMode: "total" | "by-type";
|
||||
timeSeries: { points: TimeSeriesPoint[] } | null;
|
||||
timeSeriesLoading: boolean;
|
||||
sessionLogs: SessionLogEntry[] | null;
|
||||
sessionLogsLoading: boolean;
|
||||
sessionLogsExpanded: boolean;
|
||||
logFilterRoles: SessionLogRole[];
|
||||
logFilterTools: string[];
|
||||
logFilterHasTools: boolean;
|
||||
logFilterQuery: string;
|
||||
query: string;
|
||||
queryDraft: string;
|
||||
sessionSort: "tokens" | "cost" | "recent" | "messages" | "errors";
|
||||
sessionSortDir: "asc" | "desc";
|
||||
recentSessions: string[];
|
||||
sessionsTab: "all" | "recent";
|
||||
visibleColumns: UsageColumnId[];
|
||||
timeZone: "local" | "utc";
|
||||
contextExpanded: boolean;
|
||||
headerPinned: boolean;
|
||||
onStartDateChange: (date: string) => void;
|
||||
onEndDateChange: (date: string) => void;
|
||||
onRefresh: () => void;
|
||||
onTimeZoneChange: (zone: "local" | "utc") => void;
|
||||
onToggleContextExpanded: () => void;
|
||||
onToggleHeaderPinned: () => void;
|
||||
onToggleSessionLogsExpanded: () => void;
|
||||
onLogFilterRolesChange: (next: SessionLogRole[]) => void;
|
||||
onLogFilterToolsChange: (next: string[]) => void;
|
||||
onLogFilterHasToolsChange: (next: boolean) => void;
|
||||
onLogFilterQueryChange: (next: string) => void;
|
||||
onLogFilterClear: () => void;
|
||||
onSelectSession: (key: string, shiftKey: boolean) => void;
|
||||
onChartModeChange: (mode: "tokens" | "cost") => void;
|
||||
onDailyChartModeChange: (mode: "total" | "by-type") => void;
|
||||
onTimeSeriesModeChange: (mode: "cumulative" | "per-turn") => void;
|
||||
onTimeSeriesBreakdownChange: (mode: "total" | "by-type") => void;
|
||||
onSelectDay: (day: string, shiftKey: boolean) => void; // Support shift-click
|
||||
onSelectHour: (hour: number, shiftKey: boolean) => void;
|
||||
onClearDays: () => void;
|
||||
onClearHours: () => void;
|
||||
onClearSessions: () => void;
|
||||
onClearFilters: () => void;
|
||||
onQueryDraftChange: (query: string) => void;
|
||||
onApplyQuery: () => void;
|
||||
onClearQuery: () => void;
|
||||
onSessionSortChange: (sort: "tokens" | "cost" | "recent" | "messages" | "errors") => void;
|
||||
onSessionSortDirChange: (dir: "asc" | "desc") => void;
|
||||
onSessionsTabChange: (tab: "all" | "recent") => void;
|
||||
onToggleColumn: (column: UsageColumnId) => void;
|
||||
};
|
||||
|
||||
export type SessionLogEntry = {
|
||||
timestamp: number;
|
||||
role: "user" | "assistant" | "tool" | "toolResult";
|
||||
content: string;
|
||||
tokens?: number;
|
||||
cost?: number;
|
||||
};
|
||||
|
||||
export type SessionLogRole = SessionLogEntry["role"];
|
||||
Reference in New Issue
Block a user