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