mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-17 20:21:13 +00:00
style: fix extension lint violations
This commit is contained in:
@@ -176,7 +176,7 @@ function resolveConfiguredMcpServers(params: {
|
||||
pluginToolsMcpBridge: boolean;
|
||||
moduleUrl?: string;
|
||||
}): Record<string, McpServerConfig> {
|
||||
const resolved = { ...(params.mcpServers ?? {}) };
|
||||
const resolved = { ...params.mcpServers };
|
||||
if (!params.pluginToolsMcpBridge) {
|
||||
return resolved;
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ function resolveStatusTextForTag(params: {
|
||||
}
|
||||
if (tag === "plan") {
|
||||
const entries = Array.isArray(payload.entries) ? payload.entries : [];
|
||||
const first = entries.find((entry) => isRecord(entry)) as Record<string, unknown> | undefined;
|
||||
const first = entries.find((entry) => isRecord(entry));
|
||||
const content = asTrimmedString(first?.content);
|
||||
return content ? `plan: ${content}` : null;
|
||||
}
|
||||
@@ -227,12 +227,12 @@ export function parsePromptEventLine(line: string): AcpRuntimeEvent | null {
|
||||
case "tool_call":
|
||||
return createToolCallEvent({
|
||||
payload,
|
||||
tag: (tag ?? "tool_call") as AcpSessionUpdateTag,
|
||||
tag: tag ?? "tool_call",
|
||||
});
|
||||
case "tool_call_update":
|
||||
return createToolCallEvent({
|
||||
payload,
|
||||
tag: (tag ?? "tool_call_update") as AcpSessionUpdateTag,
|
||||
tag: tag ?? "tool_call_update",
|
||||
});
|
||||
case "agent_message_chunk":
|
||||
return resolveTextChunk({
|
||||
|
||||
@@ -227,7 +227,7 @@ export async function discoverMantleModels(params: {
|
||||
contextWindow: DEFAULT_CONTEXT_WINDOW,
|
||||
maxTokens: DEFAULT_MAX_TOKENS,
|
||||
}))
|
||||
.sort((a, b) => a.id.localeCompare(b.id));
|
||||
.toSorted((a, b) => a.id.localeCompare(b.id));
|
||||
|
||||
discoveryCache.set(cacheKey, { models, fetchedAt: now() });
|
||||
return models;
|
||||
|
||||
@@ -177,12 +177,16 @@ function resolveBaseModelId(profile: InferenceProfileSummary): string | undefine
|
||||
const firstArn = profile.models?.[0]?.modelArn;
|
||||
if (firstArn) {
|
||||
const arnMatch = /foundation-model\/(.+)$/.exec(firstArn);
|
||||
if (arnMatch) return arnMatch[1];
|
||||
if (arnMatch) {
|
||||
return arnMatch[1];
|
||||
}
|
||||
}
|
||||
if (profile.type === "SYSTEM_DEFINED") {
|
||||
const id = profile.inferenceProfileId ?? "";
|
||||
const prefixMatch = /^(?:us|eu|ap|jp|global)\.(.+)$/i.exec(id);
|
||||
if (prefixMatch) return prefixMatch[1];
|
||||
if (prefixMatch) {
|
||||
return prefixMatch[1];
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@@ -236,8 +240,12 @@ function resolveInferenceProfiles(
|
||||
): ModelDefinitionConfig[] {
|
||||
const discovered: ModelDefinitionConfig[] = [];
|
||||
for (const profile of profiles) {
|
||||
if (!profile.inferenceProfileId?.trim()) continue;
|
||||
if (profile.status !== "ACTIVE") continue;
|
||||
if (!profile.inferenceProfileId?.trim()) {
|
||||
continue;
|
||||
}
|
||||
if (profile.status !== "ACTIVE") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Apply provider filter: check if any of the underlying models match.
|
||||
if (providerFilter.length > 0) {
|
||||
@@ -246,7 +254,9 @@ function resolveInferenceProfiles(
|
||||
const provider = m.modelArn?.split("/")?.[1]?.split(".")?.[0];
|
||||
return provider ? providerFilter.includes(provider.toLowerCase()) : false;
|
||||
});
|
||||
if (!matchesFilter) continue;
|
||||
if (!matchesFilter) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Look up the underlying foundation model to inherit its capabilities.
|
||||
@@ -366,7 +376,9 @@ export async function discoverBedrockModels(params: {
|
||||
return discovered.toSorted((a, b) => {
|
||||
const aGlobal = a.id.startsWith("global.") ? 0 : 1;
|
||||
const bGlobal = b.id.startsWith("global.") ? 0 : 1;
|
||||
if (aGlobal !== bGlobal) return aGlobal - bGlobal;
|
||||
if (aGlobal !== bGlobal) {
|
||||
return aGlobal - bGlobal;
|
||||
}
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
})();
|
||||
@@ -409,8 +421,8 @@ export async function resolveImplicitBedrockProvider(params: {
|
||||
}): Promise<ModelProviderConfig | null> {
|
||||
const env = params.env ?? process.env;
|
||||
const discoveryConfig = {
|
||||
...(params.config?.models?.bedrockDiscovery ?? {}),
|
||||
...(params.pluginConfig?.discovery ?? {}),
|
||||
...params.config?.models?.bedrockDiscovery,
|
||||
...params.pluginConfig?.discovery,
|
||||
};
|
||||
const enabled = discoveryConfig?.enabled;
|
||||
const hasAwsCreds = resolveAwsSdkEnvVarName(env) !== undefined;
|
||||
|
||||
@@ -33,7 +33,9 @@ async function registerWithConfig(
|
||||
});
|
||||
await amazonBedrockPlugin.register(api);
|
||||
const provider = providers[0];
|
||||
if (!provider) throw new Error("provider registration missing");
|
||||
if (!provider) {
|
||||
throw new Error("provider registration missing");
|
||||
}
|
||||
return provider;
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,9 @@ function createGuardrailWrapStreamFn(
|
||||
): (ctx: { modelId: string; streamFn?: StreamFn }) => StreamFn | null | undefined {
|
||||
return (ctx) => {
|
||||
const inner = innerWrapStreamFn(ctx);
|
||||
if (!inner) return inner;
|
||||
if (!inner) {
|
||||
return inner;
|
||||
}
|
||||
return (model, context, options) => {
|
||||
return streamWithPayloadPatch(inner, model, context, options, (payload) => {
|
||||
const gc: Record<string, unknown> = {
|
||||
@@ -88,7 +90,9 @@ export function registerAmazonBedrockPlugin(api: OpenClawPluginApi): void {
|
||||
|
||||
/** Extract the AWS region from a bedrock-runtime baseUrl. */
|
||||
function extractRegionFromBaseUrl(baseUrl: string | undefined): string | undefined {
|
||||
if (!baseUrl) return undefined;
|
||||
if (!baseUrl) {
|
||||
return undefined;
|
||||
}
|
||||
return bedrockRegionRe.exec(baseUrl)?.[1];
|
||||
}
|
||||
|
||||
@@ -108,13 +112,19 @@ export function registerAmazonBedrockPlugin(api: OpenClawPluginApi): void {
|
||||
const exact = (providers[providerId] as { baseUrl?: string } | undefined)?.baseUrl;
|
||||
if (exact) {
|
||||
const region = extractRegionFromBaseUrl(exact);
|
||||
if (region) return region;
|
||||
if (region) {
|
||||
return region;
|
||||
}
|
||||
}
|
||||
// Fall back to alias matches (e.g. "bedrock" instead of "amazon-bedrock").
|
||||
for (const [key, value] of Object.entries(providers)) {
|
||||
if (key === providerId || normalizeProviderId(key) !== providerId) continue;
|
||||
if (key === providerId || normalizeProviderId(key) !== providerId) {
|
||||
continue;
|
||||
}
|
||||
const region = extractRegionFromBaseUrl((value as { baseUrl?: string }).baseUrl);
|
||||
if (region) return region;
|
||||
if (region) {
|
||||
return region;
|
||||
}
|
||||
}
|
||||
}
|
||||
return config?.models?.bedrockDiscovery?.region;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CliBackendPlugin, CliBackendConfig } from "openclaw/plugin-sdk/cli-backend";
|
||||
import type { CliBackendPlugin } from "openclaw/plugin-sdk/cli-backend";
|
||||
import {
|
||||
CLI_FRESH_WATCHDOG_DEFAULTS,
|
||||
CLI_RESUME_WATCHDOG_DEFAULTS,
|
||||
|
||||
@@ -8,18 +8,15 @@ import type {
|
||||
} from "openclaw/plugin-sdk/plugin-entry";
|
||||
import {
|
||||
applyAuthProfileConfig,
|
||||
createProviderApiKeyAuthMethod,
|
||||
buildTokenProfileId,
|
||||
ensureApiKeyFromOptionEnvOrPrompt,
|
||||
listProfilesForProvider,
|
||||
normalizeApiKeyInput,
|
||||
type OpenClawConfig as ProviderAuthConfig,
|
||||
suggestOAuthProfileIdForLegacyDefault,
|
||||
type AuthProfileStore,
|
||||
buildTokenProfileId,
|
||||
createProviderApiKeyAuthMethod,
|
||||
listProfilesForProvider,
|
||||
type OpenClawConfig as ProviderAuthConfig,
|
||||
type ProviderAuthResult,
|
||||
suggestOAuthProfileIdForLegacyDefault,
|
||||
upsertAuthProfile,
|
||||
validateAnthropicSetupToken,
|
||||
validateApiKeyInput,
|
||||
} from "openclaw/plugin-sdk/provider-auth";
|
||||
import { cloneFirstTemplateModel } from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import { fetchClaudeUsage } from "openclaw/plugin-sdk/provider-usage";
|
||||
@@ -54,7 +51,7 @@ const ANTHROPIC_MODERN_MODEL_PREFIXES = [
|
||||
"claude-sonnet-4-5",
|
||||
"claude-haiku-4-5",
|
||||
] as const;
|
||||
const ANTHROPIC_OAUTH_ALLOWLIST = [
|
||||
const _ANTHROPIC_OAUTH_ALLOWLIST = [
|
||||
"anthropic/claude-sonnet-4-6",
|
||||
"anthropic/claude-opus-4-6",
|
||||
"anthropic/claude-opus-4-5",
|
||||
@@ -372,7 +369,7 @@ export function registerAnthropicPlugin(api: OpenClawPluginApi): void {
|
||||
const claudeCliProfileId = "anthropic:claude-cli";
|
||||
const providerId = "anthropic";
|
||||
const defaultAnthropicModel = "anthropic/claude-sonnet-4-6";
|
||||
const anthropicOauthAllowlist = [
|
||||
const _anthropicOauthAllowlist = [
|
||||
"anthropic/claude-sonnet-4-6",
|
||||
"anthropic/claude-opus-4-6",
|
||||
"anthropic/claude-opus-4-5",
|
||||
|
||||
@@ -52,7 +52,7 @@ export function resolveBlueBubblesAccount(params: {
|
||||
const merged = mergeBlueBubblesAccountConfig(params.cfg, accountId);
|
||||
const accountEnabled = merged.enabled !== false;
|
||||
const serverUrl = normalizeSecretInputString(merged.serverUrl);
|
||||
const password = normalizeSecretInputString(merged.password);
|
||||
const _password = normalizeSecretInputString(merged.password);
|
||||
const configured = Boolean(serverUrl && hasConfiguredSecretInput(merged.password));
|
||||
const baseUrl = serverUrl ? normalizeBlueBubblesServerUrl(serverUrl) : undefined;
|
||||
return {
|
||||
|
||||
@@ -10,12 +10,11 @@ import { resolveRequestUrl } from "./request-url.js";
|
||||
import type { OpenClawConfig } from "./runtime-api.js";
|
||||
import { getBlueBubblesRuntime, warnBlueBubbles } from "./runtime.js";
|
||||
import { extractBlueBubblesMessageId, resolveBlueBubblesSendTarget } from "./send-helpers.js";
|
||||
import { resolveChatGuidForTarget, createChatForHandle } from "./send.js";
|
||||
import { createChatForHandle, resolveChatGuidForTarget } from "./send.js";
|
||||
import {
|
||||
blueBubblesFetchWithTimeout,
|
||||
buildBlueBubblesApiUrl,
|
||||
type BlueBubblesAttachment,
|
||||
type BlueBubblesSendTarget,
|
||||
type SsrFPolicy,
|
||||
} from "./types.js";
|
||||
|
||||
@@ -127,10 +126,12 @@ export async function downloadBlueBubblesAttachment(
|
||||
};
|
||||
} catch (error) {
|
||||
if (readMediaFetchErrorCode(error) === "max_bytes") {
|
||||
throw new Error(`BlueBubbles attachment too large (limit ${maxBytes} bytes)`);
|
||||
throw new Error(`BlueBubbles attachment too large (limit ${maxBytes} bytes)`, {
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
const text = error instanceof Error ? error.message : String(error);
|
||||
throw new Error(`BlueBubbles attachment download failed: ${text}`);
|
||||
throw new Error(`BlueBubbles attachment download failed: ${text}`, { cause: error });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
createOpenGroupPolicyRestrictSendersWarningCollector,
|
||||
projectAccountWarningCollector,
|
||||
} from "openclaw/plugin-sdk/channel-policy";
|
||||
import { createAttachedChannelResultAdapter } from "openclaw/plugin-sdk/channel-send-result";
|
||||
import {
|
||||
buildProbeChannelStatusSummary,
|
||||
PAIRING_APPROVED_MESSAGE,
|
||||
@@ -18,20 +17,15 @@ import {
|
||||
createComputedAccountStatusAdapter,
|
||||
createDefaultChannelRuntimeState,
|
||||
} from "openclaw/plugin-sdk/status-helpers";
|
||||
import {
|
||||
listBlueBubblesAccountIds,
|
||||
type ResolvedBlueBubblesAccount,
|
||||
resolveBlueBubblesAccount,
|
||||
resolveDefaultBlueBubblesAccountId,
|
||||
} from "./accounts.js";
|
||||
import { type ResolvedBlueBubblesAccount } from "./accounts.js";
|
||||
import { bluebubblesMessageActions } from "./actions.js";
|
||||
import {
|
||||
bluebubblesCapabilities,
|
||||
bluebubblesConfigAdapter,
|
||||
bluebubblesConfigSchema,
|
||||
bluebubblesMeta as meta,
|
||||
bluebubblesReload,
|
||||
describeBlueBubblesAccount,
|
||||
bluebubblesMeta as meta,
|
||||
} from "./channel-shared.js";
|
||||
import type { BlueBubblesProbe } from "./channel.runtime.js";
|
||||
import { createBlueBubblesConversationBindingManager } from "./conversation-bindings.js";
|
||||
|
||||
@@ -202,7 +202,7 @@ export function createBlueBubblesConversationBindingManager(params: {
|
||||
},
|
||||
unbindBySessionKey: (targetSessionKey) => {
|
||||
const removed: BlueBubblesConversationBindingRecord[] = [];
|
||||
for (const record of [...getState().bindingsByAccountConversation.values()]) {
|
||||
for (const record of getState().bindingsByAccountConversation.values()) {
|
||||
if (record.accountId !== accountId || record.targetSessionKey !== targetSessionKey) {
|
||||
continue;
|
||||
}
|
||||
@@ -214,7 +214,7 @@ export function createBlueBubblesConversationBindingManager(params: {
|
||||
return removed;
|
||||
},
|
||||
stop: () => {
|
||||
for (const key of [...getState().bindingsByAccountConversation.keys()]) {
|
||||
for (const key of getState().bindingsByAccountConversation.keys()) {
|
||||
if (key.startsWith(`${accountId}:`)) {
|
||||
getState().bindingsByAccountConversation.delete(key);
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ export async function fetchBlueBubblesHistory(
|
||||
entries: historyEntries.slice(0, effectiveLimit), // Ensure we don't exceed the requested limit
|
||||
resolved: true,
|
||||
};
|
||||
} catch (error) {
|
||||
} catch {
|
||||
// Continue to next path
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ export function rememberBlueBubblesReplyCache(
|
||||
break;
|
||||
}
|
||||
while (blueBubblesReplyCacheByMessageId.size > REPLY_CACHE_MAX) {
|
||||
const oldest = blueBubblesReplyCacheByMessageId.keys().next().value as string | undefined;
|
||||
const oldest = blueBubblesReplyCacheByMessageId.keys().next().value;
|
||||
if (!oldest) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/core";
|
||||
import type { ResolvedBlueBubblesAccount } from "./accounts.js";
|
||||
import { getBlueBubblesRuntime } from "./runtime.js";
|
||||
import type { BlueBubblesAccountConfig } from "./types.js";
|
||||
export {
|
||||
DEFAULT_WEBHOOK_PATH,
|
||||
normalizeWebhookPath,
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { ResolvedBlueBubblesAccount } from "./accounts.js";
|
||||
import { fetchBlueBubblesHistory } from "./history.js";
|
||||
import { createBlueBubblesDebounceRegistry } from "./monitor-debounce.js";
|
||||
import type { NormalizedWebhookMessage } from "./monitor-normalize.js";
|
||||
import { resetBlueBubblesSelfChatCache } from "./monitor-self-chat-cache.js";
|
||||
import { handleBlueBubblesWebhookRequest, resolveBlueBubblesMessageId } from "./monitor.js";
|
||||
import { resolveBlueBubblesMessageId } from "./monitor.js";
|
||||
import {
|
||||
createMockAccount,
|
||||
createNewMessagePayloadForTest,
|
||||
createMockRequest,
|
||||
createTimestampedNewMessagePayloadForTest,
|
||||
createNewMessagePayloadForTest,
|
||||
createTimestampedMessageReactionPayloadForTest,
|
||||
dispatchWebhookRequestForTest,
|
||||
createTimestampedNewMessagePayloadForTest,
|
||||
dispatchWebhookPayloadForTest,
|
||||
flushAsync,
|
||||
dispatchWebhookRequestForTest,
|
||||
setupWebhookTargetForTest,
|
||||
setupWebhookTargetsForTest,
|
||||
trackWebhookRegistrationForTest,
|
||||
@@ -805,7 +803,7 @@ describe("BlueBubbles webhook monitor", () => {
|
||||
const core = createMockRuntime();
|
||||
installTimingAwareInboundDebouncer(core);
|
||||
|
||||
const registration = trackWebhookRegistrationForTest(
|
||||
const _registration = trackWebhookRegistrationForTest(
|
||||
setupWebhookTargetForTest({
|
||||
createCore: createMockRuntime,
|
||||
core,
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { ResolvedBlueBubblesAccount } from "./accounts.js";
|
||||
import { fetchBlueBubblesHistory } from "./history.js";
|
||||
import { handleBlueBubblesWebhookRequest, resolveBlueBubblesMessageId } from "./monitor.js";
|
||||
import {
|
||||
LOOPBACK_REMOTE_ADDRESSES_FOR_TEST,
|
||||
createWebhookDispatchForTest,
|
||||
createMockAccount,
|
||||
createHangingWebhookRequestForTest,
|
||||
createLoopbackWebhookRequestParamsForTest,
|
||||
createMockAccount,
|
||||
createPasswordQueryRequestParamsForTest,
|
||||
createProtectedWebhookAccountForTest,
|
||||
createRemoteWebhookRequestParamsForTest,
|
||||
createTimestampedNewMessagePayloadForTest,
|
||||
createWebhookDispatchForTest,
|
||||
dispatchWebhookPayloadForTest,
|
||||
expectWebhookRequestStatusForTest,
|
||||
expectWebhookStatusForTest,
|
||||
LOOPBACK_REMOTE_ADDRESSES_FOR_TEST,
|
||||
setupWebhookTargetForTest,
|
||||
setupWebhookTargetsForTest,
|
||||
trackWebhookRegistrationForTest,
|
||||
|
||||
@@ -8,11 +8,7 @@ import {
|
||||
type ChannelSetupWizard,
|
||||
type OpenClawConfig,
|
||||
} from "openclaw/plugin-sdk/setup";
|
||||
import {
|
||||
listBlueBubblesAccountIds,
|
||||
resolveBlueBubblesAccount,
|
||||
resolveDefaultBlueBubblesAccountId,
|
||||
} from "./accounts.js";
|
||||
import { resolveBlueBubblesAccount, resolveDefaultBlueBubblesAccountId } from "./accounts.js";
|
||||
import { applyBlueBubblesConnectionConfig } from "./config-apply.js";
|
||||
import { hasConfiguredSecretInput, normalizeSecretInputString } from "./secret-input.js";
|
||||
import {
|
||||
@@ -80,7 +76,7 @@ const promptBlueBubblesAllowFrom = createPromptParsedAllowFromForAccount({
|
||||
});
|
||||
|
||||
function validateBlueBubblesServerUrlInput(value: unknown): string | undefined {
|
||||
const trimmed = String(value ?? "").trim();
|
||||
const trimmed = typeof value === "string" ? value.trim() : "";
|
||||
if (!trimmed) {
|
||||
return "Required";
|
||||
}
|
||||
|
||||
@@ -457,7 +457,7 @@ function createBraveToolDefinition(
|
||||
return missingBraveKeyPayload();
|
||||
}
|
||||
|
||||
const params = args as Record<string, unknown>;
|
||||
const params = args;
|
||||
const query = readStringParam(params, "query", { required: true });
|
||||
const count =
|
||||
readNumberParam(params, "count", { integer: true }) ??
|
||||
|
||||
@@ -12,17 +12,17 @@ import {
|
||||
validateApiKeyInput,
|
||||
} from "openclaw/plugin-sdk/provider-auth";
|
||||
import { buildCloudflareAiGatewayCatalogProvider } from "./catalog-provider.js";
|
||||
import {
|
||||
buildCloudflareAiGatewayModelDefinition,
|
||||
CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF,
|
||||
resolveCloudflareAiGatewayBaseUrl,
|
||||
} from "./models.js";
|
||||
import { CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF } from "./models.js";
|
||||
import { applyCloudflareAiGatewayConfig, buildCloudflareAiGatewayConfigPatch } from "./onboard.js";
|
||||
|
||||
const PROVIDER_ID = "cloudflare-ai-gateway";
|
||||
const PROVIDER_ENV_VAR = "CLOUDFLARE_AI_GATEWAY_API_KEY";
|
||||
const PROFILE_ID = "cloudflare-ai-gateway:default";
|
||||
|
||||
function readRequiredTextInput(value: unknown): string {
|
||||
return typeof value === "string" ? value.trim() : "";
|
||||
}
|
||||
|
||||
async function resolveCloudflareGatewayMetadataInteractive(ctx: {
|
||||
accountId?: string;
|
||||
gatewayId?: string;
|
||||
@@ -38,16 +38,16 @@ async function resolveCloudflareGatewayMetadataInteractive(ctx: {
|
||||
if (!accountId) {
|
||||
const value = await ctx.prompter.text({
|
||||
message: "Enter Cloudflare Account ID",
|
||||
validate: (val) => (String(val ?? "").trim() ? undefined : "Account ID is required"),
|
||||
validate: (val) => (readRequiredTextInput(val) ? undefined : "Account ID is required"),
|
||||
});
|
||||
accountId = String(value ?? "").trim();
|
||||
accountId = readRequiredTextInput(value);
|
||||
}
|
||||
if (!gatewayId) {
|
||||
const value = await ctx.prompter.text({
|
||||
message: "Enter Cloudflare AI Gateway ID",
|
||||
validate: (val) => (String(val ?? "").trim() ? undefined : "Gateway ID is required"),
|
||||
validate: (val) => (readRequiredTextInput(val) ? undefined : "Gateway ID is required"),
|
||||
});
|
||||
gatewayId = String(value ?? "").trim();
|
||||
gatewayId = readRequiredTextInput(value);
|
||||
}
|
||||
return { accountId, gatewayId };
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ function withPluginsEnabled<T>(cfg: T): T {
|
||||
return {
|
||||
...record,
|
||||
plugins: {
|
||||
...(record.plugins && typeof record.plugins === "object" ? (record.plugins as object) : {}),
|
||||
...(record.plugins && typeof record.plugins === "object" ? record.plugins : {}),
|
||||
enabled: true,
|
||||
},
|
||||
} as T;
|
||||
|
||||
@@ -80,11 +80,11 @@ function createApi(params?: {
|
||||
},
|
||||
pluginConfig: {
|
||||
publicUrl: "ws://51.79.175.165:18789",
|
||||
...(params?.pluginConfig ?? {}),
|
||||
...params?.pluginConfig,
|
||||
},
|
||||
runtime: (params?.runtime ?? {}) as OpenClawPluginApi["runtime"],
|
||||
registerCommand: params?.registerCommand,
|
||||
}) as OpenClawPluginApi;
|
||||
});
|
||||
}
|
||||
|
||||
function registerPairCommand(params?: {
|
||||
|
||||
@@ -106,7 +106,7 @@ describe("PlaywrightDiffScreenshotter", () => {
|
||||
});
|
||||
|
||||
it("renders PDF output when format is pdf", async () => {
|
||||
const { pages, browser, screenshotter } = await createScreenshotterHarness();
|
||||
const { pages, _browser, screenshotter } = await createScreenshotterHarness();
|
||||
const pdfPath = path.join(rootDir, "preview.pdf");
|
||||
|
||||
await screenshotter.screenshotHtml({
|
||||
|
||||
@@ -258,6 +258,7 @@ export class PlaywrightDiffScreenshotter implements DiffScreenshotter {
|
||||
const reason = error instanceof Error ? error.message : String(error);
|
||||
throw new Error(
|
||||
`Diff PNG/PDF rendering requires a Chromium-compatible browser. Set browser.executablePath or install Chrome/Chromium. ${reason}`,
|
||||
{ cause: error },
|
||||
);
|
||||
} finally {
|
||||
await page?.close().catch(() => {});
|
||||
|
||||
@@ -80,7 +80,7 @@ function createApi(): OpenClawPluginApi {
|
||||
},
|
||||
},
|
||||
runtime: {} as OpenClawPluginApi["runtime"],
|
||||
}) as OpenClawPluginApi;
|
||||
});
|
||||
}
|
||||
|
||||
function createPngScreenshotter(
|
||||
|
||||
@@ -485,7 +485,7 @@ function createApi(pluginConfig?: Record<string, unknown>): OpenClawPluginApi {
|
||||
},
|
||||
pluginConfig,
|
||||
runtime: {} as OpenClawPluginApi["runtime"],
|
||||
}) as OpenClawPluginApi;
|
||||
});
|
||||
}
|
||||
|
||||
function createToolWithScreenshotter(
|
||||
|
||||
@@ -33,11 +33,11 @@ import { getDiscordApprovalCapability } from "./approval-native.js";
|
||||
import { discordMessageActions as discordMessageActionsImpl } from "./channel-actions.js";
|
||||
import {
|
||||
buildTokenChannelStatusSummary,
|
||||
type ChannelPlugin,
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
PAIRING_APPROVED_MESSAGE,
|
||||
projectCredentialSnapshotFields,
|
||||
resolveConfiguredFromCredentialStatuses,
|
||||
type ChannelPlugin,
|
||||
type OpenClawConfig,
|
||||
} from "./channel-api.js";
|
||||
import { shouldSuppressLocalDiscordExecApprovalPrompt } from "./exec-approvals.js";
|
||||
|
||||
@@ -11,8 +11,7 @@ import {
|
||||
import { ChannelType } from "discord-api-types/v10";
|
||||
import { createChannelPairingChallengeIssuer } from "openclaw/plugin-sdk/channel-pairing";
|
||||
import { resolveCommandAuthorizedFromAuthorizers } from "openclaw/plugin-sdk/command-auth-native";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { DiscordAccountConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { DiscordAccountConfig, OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/dangerous-name-runtime";
|
||||
import { resolveAgentRoute } from "openclaw/plugin-sdk/routing";
|
||||
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
||||
@@ -33,12 +32,12 @@ import {
|
||||
isDiscordGroupAllowedByPolicy,
|
||||
normalizeDiscordAllowList,
|
||||
normalizeDiscordSlug,
|
||||
resolveGroupDmAllow,
|
||||
resolveDiscordAllowListMatch,
|
||||
resolveDiscordChannelConfigWithFallback,
|
||||
resolveDiscordGuildEntry,
|
||||
resolveDiscordMemberAccessState,
|
||||
resolveDiscordOwnerAccess,
|
||||
resolveGroupDmAllow,
|
||||
} from "./allow-list.js";
|
||||
import { formatDiscordUserTag } from "./format.js";
|
||||
|
||||
|
||||
@@ -26,8 +26,7 @@ import {
|
||||
import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/dangerous-name-runtime";
|
||||
import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
|
||||
import { getAgentScopedMediaLocalRoots } from "openclaw/plugin-sdk/media-runtime";
|
||||
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { createNonExitingRuntime } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { createNonExitingRuntime, logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { resolveOpenProviderRuntimeGroupPolicy } from "openclaw/plugin-sdk/runtime-group-policy";
|
||||
import { logDebug, logError } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { resolveDiscordMaxLinesPerMessage } from "../accounts.js";
|
||||
@@ -36,8 +35,10 @@ import {
|
||||
parseDiscordModalCustomIdForCarbon,
|
||||
} from "../component-custom-id.js";
|
||||
import { resolveDiscordComponentEntry, resolveDiscordModalEntry } from "../components-registry.js";
|
||||
import { type DiscordInteractiveHandlerContext } from "../interactive-dispatch.js";
|
||||
import { dispatchDiscordPluginInteractiveHandler } from "../interactive-dispatch.js";
|
||||
import {
|
||||
dispatchDiscordPluginInteractiveHandler,
|
||||
type DiscordInteractiveHandlerContext,
|
||||
} from "../interactive-dispatch.js";
|
||||
import { editDiscordComponentMessage } from "../send.components.js";
|
||||
import {
|
||||
AGENT_BUTTON_KEY,
|
||||
@@ -56,14 +57,17 @@ import {
|
||||
parseDiscordModalId,
|
||||
resolveAgentComponentRoute,
|
||||
resolveComponentCommandAuthorized,
|
||||
type ComponentInteractionContext,
|
||||
resolveDiscordChannelContext,
|
||||
type DiscordChannelContext,
|
||||
resolveDiscordInteractionId,
|
||||
resolveInteractionContextWithDmAuth,
|
||||
resolveInteractionCustomId,
|
||||
resolveModalFieldValues,
|
||||
resolvePinnedMainDmOwnerFromAllowlist,
|
||||
type AgentComponentContext,
|
||||
type AgentComponentInteraction,
|
||||
type AgentComponentMessageInteraction,
|
||||
type ComponentInteractionContext,
|
||||
type DiscordChannelContext,
|
||||
} from "./agent-components-helpers.js";
|
||||
import {
|
||||
enqueueSystemEvent,
|
||||
@@ -77,8 +81,8 @@ import {
|
||||
} from "./allow-list.js";
|
||||
import { formatDiscordUserTag } from "./format.js";
|
||||
import {
|
||||
buildDiscordInboundAccessContext,
|
||||
buildDiscordGroupSystemPrompt,
|
||||
buildDiscordInboundAccessContext,
|
||||
} from "./inbound-context.js";
|
||||
import { buildDirectLabel, buildGuildLabel } from "./reply-context.js";
|
||||
import { deliverDiscordReply } from "./reply-delivery.js";
|
||||
@@ -100,6 +104,10 @@ async function loadComponentsRuntime() {
|
||||
return await componentsRuntimePromise;
|
||||
}
|
||||
|
||||
async function loadReplyRuntime() {
|
||||
replyRuntimePromise ??= import("openclaw/plugin-sdk/reply-runtime");
|
||||
return await replyRuntimePromise;
|
||||
}
|
||||
async function loadReplyPipelineRuntime() {
|
||||
replyPipelineRuntimePromise ??= import("openclaw/plugin-sdk/channel-reply-pipeline");
|
||||
return await replyPipelineRuntimePromise;
|
||||
|
||||
@@ -10,15 +10,7 @@ import {
|
||||
type TopLevelComponents,
|
||||
} from "@buape/carbon";
|
||||
import { ButtonStyle, Routes } from "discord-api-types/v10";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { DiscordExecApprovalConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import {
|
||||
createChannelNativeApprovalRuntime,
|
||||
type ExecApprovalChannelRuntime,
|
||||
} from "openclaw/plugin-sdk/infra-runtime";
|
||||
import { buildExecApprovalActionDescriptors } from "openclaw/plugin-sdk/infra-runtime";
|
||||
import { resolveExecApprovalCommandDisplay } from "openclaw/plugin-sdk/infra-runtime";
|
||||
import { getExecApprovalApproverDmNoticeText } from "openclaw/plugin-sdk/infra-runtime";
|
||||
import type { DiscordExecApprovalConfig, OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type {
|
||||
ExecApprovalActionDescriptor,
|
||||
ExecApprovalDecision,
|
||||
@@ -27,6 +19,13 @@ import type {
|
||||
PluginApprovalRequest,
|
||||
PluginApprovalResolved,
|
||||
} from "openclaw/plugin-sdk/infra-runtime";
|
||||
import {
|
||||
buildExecApprovalActionDescriptors,
|
||||
createChannelNativeApprovalRuntime,
|
||||
getExecApprovalApproverDmNoticeText,
|
||||
resolveExecApprovalCommandDisplay,
|
||||
type ExecApprovalChannelRuntime,
|
||||
} from "openclaw/plugin-sdk/infra-runtime";
|
||||
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { logDebug, logError } from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
@@ -560,7 +559,12 @@ export class DiscordExecApprovalHandler {
|
||||
},
|
||||
};
|
||||
},
|
||||
deliverTarget: async ({ plannedTarget, preparedTarget, pendingContent }) => {
|
||||
deliverTarget: async ({
|
||||
plannedTarget,
|
||||
preparedTarget,
|
||||
pendingContent,
|
||||
request: _request,
|
||||
}) => {
|
||||
const { rest, request: discordRequest } = createDiscordClient(
|
||||
{ token: this.opts.token, accountId: this.opts.accountId },
|
||||
this.opts.cfg,
|
||||
|
||||
@@ -187,13 +187,12 @@ function createAllowedGuildEntries(requireMention = false) {
|
||||
|
||||
function createHydratedGuildClient(restPayload: Record<string, unknown>) {
|
||||
const restGet = vi.fn(async () => restPayload);
|
||||
const baseClient = createGuildTextClient(CHANNEL_ID);
|
||||
const client = {
|
||||
...Object.assign(Object.create(Object.getPrototypeOf(baseClient)), baseClient),
|
||||
const client = Object.assign(createGuildTextClient(CHANNEL_ID), {
|
||||
rest: {
|
||||
get: restGet,
|
||||
},
|
||||
} as unknown as Parameters<typeof preflightDiscordMessage>[0]["client"];
|
||||
});
|
||||
}) as unknown as Parameters<typeof preflightDiscordMessage>[0]["client"];
|
||||
return { client, restGet };
|
||||
}
|
||||
|
||||
|
||||
@@ -480,13 +480,12 @@ describe("preflightDiscordMessage", () => {
|
||||
bot: true,
|
||||
},
|
||||
}));
|
||||
const baseClient = createThreadClient({ threadId, parentId });
|
||||
const client = {
|
||||
...Object.assign(Object.create(Object.getPrototypeOf(baseClient)), baseClient),
|
||||
const client = Object.assign(createThreadClient({ threadId, parentId }), {
|
||||
rest: {
|
||||
get: restGet,
|
||||
},
|
||||
} as unknown as DiscordClient;
|
||||
});
|
||||
}) as unknown as DiscordClient;
|
||||
|
||||
const result = await preflightDiscordMessage({
|
||||
...createPreflightArgs({
|
||||
|
||||
@@ -10,16 +10,14 @@ import {
|
||||
import { resolveControlCommandGate } from "openclaw/plugin-sdk/command-auth-native";
|
||||
import { hasControlCommand } from "openclaw/plugin-sdk/command-detection";
|
||||
import { shouldHandleTextCommands } from "openclaw/plugin-sdk/command-surface";
|
||||
import { loadConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { isDangerousNameMatchingEnabled, loadConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { SessionBindingRecord } from "openclaw/plugin-sdk/conversation-binding-runtime";
|
||||
import { enqueueSystemEvent, recordChannelActivity } from "openclaw/plugin-sdk/infra-runtime";
|
||||
import {
|
||||
recordPendingHistoryEntryIfEnabled,
|
||||
type HistoryEntry,
|
||||
} from "openclaw/plugin-sdk/reply-history";
|
||||
import { logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { getChildLogger } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { getChildLogger, logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { logDebug } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { resolveDefaultDiscordAccountId } from "../accounts.js";
|
||||
import {
|
||||
@@ -271,31 +269,42 @@ function mergeFetchedDiscordMessage(base: Message, fetched: APIMessage): Message
|
||||
globalName: mention.global_name ?? undefined,
|
||||
}))
|
||||
: undefined;
|
||||
const baseReferencedMessage = (base as { referencedMessage?: object }).referencedMessage;
|
||||
const assignWithPrototype = <T extends object>(baseObject: T, ...sources: object[]): T =>
|
||||
Object.assign(
|
||||
Object.create(Object.getPrototypeOf(baseObject) ?? Object.prototype),
|
||||
baseObject,
|
||||
...sources,
|
||||
) as T;
|
||||
const referencedMessage = fetched.referenced_message
|
||||
? ({
|
||||
...baseReferencedMessage,
|
||||
...fetched.referenced_message,
|
||||
mentionedUsers: Array.isArray(fetched.referenced_message.mentions)
|
||||
? fetched.referenced_message.mentions.map((mention) => ({
|
||||
...mention,
|
||||
globalName: mention.global_name ?? undefined,
|
||||
}))
|
||||
: (baseReferenced?.mentionedUsers ?? []),
|
||||
mentionedRoles:
|
||||
fetched.referenced_message.mention_roles ?? baseReferenced?.mentionedRoles ?? [],
|
||||
mentionedEveryone:
|
||||
fetched.referenced_message.mention_everyone ?? baseReferenced?.mentionedEveryone ?? false,
|
||||
} satisfies Record<string, unknown>)
|
||||
? assignWithPrototype(
|
||||
((base as { referencedMessage?: Message }).referencedMessage ?? {}) as Message,
|
||||
fetched.referenced_message,
|
||||
{
|
||||
mentionedUsers: Array.isArray(fetched.referenced_message.mentions)
|
||||
? fetched.referenced_message.mentions.map((mention) => ({
|
||||
...mention,
|
||||
globalName: mention.global_name ?? undefined,
|
||||
}))
|
||||
: (baseReferenced?.mentionedUsers ?? []),
|
||||
mentionedRoles:
|
||||
fetched.referenced_message.mention_roles ?? baseReferenced?.mentionedRoles ?? [],
|
||||
mentionedEveryone:
|
||||
fetched.referenced_message.mention_everyone ??
|
||||
baseReferenced?.mentionedEveryone ??
|
||||
false,
|
||||
} satisfies Record<string, unknown>,
|
||||
)
|
||||
: (base as { referencedMessage?: Message }).referencedMessage;
|
||||
const baseRawData = (base as { rawData?: Record<string, unknown> }).rawData;
|
||||
const rawData = {
|
||||
...baseRawData,
|
||||
message_snapshots: fetched.message_snapshots ?? baseRawData?.message_snapshots,
|
||||
...(base as { rawData?: Record<string, unknown> }).rawData,
|
||||
message_snapshots:
|
||||
fetched.message_snapshots ??
|
||||
(base as { rawData?: { message_snapshots?: unknown } }).rawData?.message_snapshots,
|
||||
sticker_items:
|
||||
(fetched as { sticker_items?: unknown }).sticker_items ?? baseRawData?.sticker_items,
|
||||
};
|
||||
return Object.assign(Object.create(Object.getPrototypeOf(base)), base, fetched, {
|
||||
return assignWithPrototype(base, fetched, {
|
||||
content: fetched.content ?? base.content,
|
||||
attachments: fetched.attachments ?? base.attachments,
|
||||
embeds: fetched.embeds ?? base.embeds,
|
||||
@@ -308,7 +317,7 @@ function mergeFetchedDiscordMessage(base: Message, fetched: APIMessage): Message
|
||||
mentionedEveryone: fetched.mention_everyone ?? base.mentionedEveryone,
|
||||
referencedMessage,
|
||||
rawData,
|
||||
}) as Message;
|
||||
}) as unknown as Message;
|
||||
}
|
||||
|
||||
async function hydrateDiscordMessageIfEmpty(params: {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { ChannelType, type RequestClient } from "@buape/carbon";
|
||||
import { resolveAckReaction, resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
|
||||
import { EmbeddedBlockChunker } from "openclaw/plugin-sdk/agent-runtime";
|
||||
import {
|
||||
EmbeddedBlockChunker,
|
||||
resolveAckReaction,
|
||||
resolveHumanDelayConfig,
|
||||
} from "openclaw/plugin-sdk/agent-runtime";
|
||||
import {
|
||||
createStatusReactionController,
|
||||
DEFAULT_TIMING,
|
||||
@@ -14,28 +17,32 @@ import {
|
||||
} from "openclaw/plugin-sdk/channel-inbound";
|
||||
import { createChannelReplyPipeline } from "openclaw/plugin-sdk/channel-reply-pipeline";
|
||||
import { resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-streaming";
|
||||
import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { resolveChannelContextVisibilityMode } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { readSessionUpdatedAt, resolveStorePath } from "openclaw/plugin-sdk/config-runtime";
|
||||
import {
|
||||
isDangerousNameMatchingEnabled,
|
||||
readSessionUpdatedAt,
|
||||
resolveChannelContextVisibilityMode,
|
||||
resolveMarkdownTableMode,
|
||||
resolveStorePath,
|
||||
} from "openclaw/plugin-sdk/config-runtime";
|
||||
import { recordInboundSession } from "openclaw/plugin-sdk/conversation-runtime";
|
||||
import { getAgentScopedMediaLocalRoots } from "openclaw/plugin-sdk/media-runtime";
|
||||
import { resolveChunkMode } from "openclaw/plugin-sdk/reply-chunking";
|
||||
import { finalizeInboundContext } from "openclaw/plugin-sdk/reply-dispatch-runtime";
|
||||
import type { ReplyPayload } from "openclaw/plugin-sdk/reply-dispatch-runtime";
|
||||
import { finalizeInboundContext } from "openclaw/plugin-sdk/reply-dispatch-runtime";
|
||||
import {
|
||||
buildPendingHistoryContextFromMap,
|
||||
clearHistoryEntriesIfEnabled,
|
||||
} from "openclaw/plugin-sdk/reply-history";
|
||||
import { resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
|
||||
import { buildAgentSessionKey } from "openclaw/plugin-sdk/routing";
|
||||
import { resolveThreadSessionKeys } from "openclaw/plugin-sdk/routing";
|
||||
import { buildAgentSessionKey, resolveThreadSessionKeys } from "openclaw/plugin-sdk/routing";
|
||||
import { danger, logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { evaluateSupplementalContextVisibility } from "openclaw/plugin-sdk/security-runtime";
|
||||
import { convertMarkdownTables } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { stripInlineDirectiveTagsForDelivery } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { stripReasoningTagsFromText } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
convertMarkdownTables,
|
||||
stripInlineDirectiveTagsForDelivery,
|
||||
stripReasoningTagsFromText,
|
||||
truncateUtf16Safe,
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
import { resolveDiscordMaxLinesPerMessage } from "../accounts.js";
|
||||
import { chunkDiscordTextWithMode } from "../chunk.js";
|
||||
import { resolveDiscordDraftStreamingChunking } from "../draft-chunking.js";
|
||||
|
||||
@@ -5,8 +5,7 @@ import type {
|
||||
StringSelectMenuInteraction,
|
||||
} from "@buape/carbon";
|
||||
import { ChannelType } from "discord-api-types/v10";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { DiscordAccountConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { DiscordAccountConfig, OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { buildPluginBindingApprovalCustomId } from "openclaw/plugin-sdk/conversation-runtime";
|
||||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { type DiscordComponentEntry, type DiscordModalEntry } from "../components.js";
|
||||
|
||||
@@ -3,12 +3,12 @@ import {
|
||||
ChannelType,
|
||||
Command,
|
||||
StringSelectMenu,
|
||||
type TopLevelComponents,
|
||||
type AutocompleteInteraction,
|
||||
type ButtonInteraction,
|
||||
type CommandInteraction,
|
||||
type CommandOptions,
|
||||
type StringSelectMenuInteraction,
|
||||
type TopLevelComponents,
|
||||
} from "@buape/carbon";
|
||||
import { ApplicationCommandOptionType } from "discord-api-types/v10";
|
||||
import { resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
|
||||
@@ -45,8 +45,7 @@ import {
|
||||
resolveSendableOutboundReplyParts,
|
||||
resolveTextChunksWithFallback,
|
||||
} from "openclaw/plugin-sdk/reply-payload";
|
||||
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { createSubsystemLogger, logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { resolveOpenProviderRuntimeGroupPolicy } from "openclaw/plugin-sdk/runtime-group-policy";
|
||||
import { loadWebMedia } from "openclaw/plugin-sdk/web-media";
|
||||
import { resolveDiscordMaxLinesPerMessage } from "../accounts.js";
|
||||
@@ -54,13 +53,13 @@ import { chunkDiscordTextWithMode } from "../chunk.js";
|
||||
import {
|
||||
normalizeDiscordAllowList,
|
||||
normalizeDiscordSlug,
|
||||
resolveDiscordChannelPolicyCommandAuthorizer,
|
||||
resolveGroupDmAllow,
|
||||
resolveDiscordChannelConfigWithFallback,
|
||||
resolveDiscordAllowListMatch,
|
||||
resolveDiscordChannelConfigWithFallback,
|
||||
resolveDiscordChannelPolicyCommandAuthorizer,
|
||||
resolveDiscordGuildEntry,
|
||||
resolveDiscordMemberAccessState,
|
||||
resolveDiscordOwnerAccess,
|
||||
resolveGroupDmAllow,
|
||||
} from "./allow-list.js";
|
||||
import { resolveDiscordDmCommandAccess } from "./dm-command-auth.js";
|
||||
import { handleDiscordDmCommandDecision } from "./dm-command-decision.js";
|
||||
|
||||
@@ -16,6 +16,7 @@ const {
|
||||
clientFetchUserMock,
|
||||
clientGetPluginMock,
|
||||
clientHandleDeployRequestMock,
|
||||
_createDiscordAutoPresenceControllerMock,
|
||||
createDiscordMessageHandlerMock,
|
||||
createDiscordNativeCommandMock,
|
||||
createdBindingManagers,
|
||||
@@ -28,6 +29,7 @@ const {
|
||||
listSkillCommandsForAgentsMock,
|
||||
monitorLifecycleMock,
|
||||
reconcileAcpThreadBindingsOnStartupMock,
|
||||
_resolveDiscordAllowlistConfigMock,
|
||||
resolveDiscordAccountMock,
|
||||
resolveNativeCommandsEnabledMock,
|
||||
resolveNativeSkillsEnabledMock,
|
||||
|
||||
@@ -672,3 +672,34 @@ export function resolveDiscordReplyDeliveryPlan(params: {
|
||||
});
|
||||
return { deliverTarget, replyTarget, replyReference };
|
||||
}
|
||||
/**
|
||||
* Extract text from forwarded message snapshots for thread starter resolution.
|
||||
* Discord forwarded messages have empty `content` and store the original text
|
||||
* in `message_snapshots[0].message.content`.
|
||||
*/
|
||||
function resolveStarterForwardedText(
|
||||
snapshots?: Array<{
|
||||
message?: {
|
||||
content?: string | null;
|
||||
attachments?: unknown[];
|
||||
embeds?: Array<{ title?: string | null; description?: string | null }>;
|
||||
sticker_items?: unknown[];
|
||||
};
|
||||
}>,
|
||||
): string {
|
||||
if (!Array.isArray(snapshots) || snapshots.length === 0) {
|
||||
return "";
|
||||
}
|
||||
const blocks: string[] = [];
|
||||
for (const snapshot of snapshots) {
|
||||
const msg = snapshot.message;
|
||||
if (!msg) {
|
||||
continue;
|
||||
}
|
||||
const text = msg.content?.trim() || resolveDiscordEmbedText(msg.embeds?.[0]) || "";
|
||||
if (text) {
|
||||
blocks.push(`[Forwarded message]\n${text}`);
|
||||
}
|
||||
}
|
||||
return blocks.join("\n\n");
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
|
||||
import type { DiscordGuildEntry } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { DiscordGuildEntry, OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { ChannelSetupDmPolicy, ChannelSetupWizard } from "openclaw/plugin-sdk/setup-runtime";
|
||||
import { createStandardChannelSetupStatus } from "openclaw/plugin-sdk/setup-runtime";
|
||||
import { formatDocsLink } from "openclaw/plugin-sdk/setup-tools";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
type ChannelSetupWizard,
|
||||
type OpenClawConfig,
|
||||
type WizardPrompter,
|
||||
type ChannelSetupWizard,
|
||||
} from "openclaw/plugin-sdk/setup-runtime";
|
||||
import { formatDocsLink } from "openclaw/plugin-sdk/setup-tools";
|
||||
import { resolveDiscordChannelAllowlist } from "./resolve-channels.js";
|
||||
|
||||
@@ -12,7 +12,7 @@ vi.mock("./ddg-client.js", () => ({
|
||||
describe("duckduckgo web search provider", () => {
|
||||
let createDuckDuckGoWebSearchProvider: typeof import("./ddg-search-provider.js").createDuckDuckGoWebSearchProvider;
|
||||
let ddgClientTesting: typeof import("./ddg-client.js").__testing;
|
||||
let plugin: typeof import("../index.js").default;
|
||||
let _plugin: typeof import("../index.js").default;
|
||||
|
||||
beforeAll(async () => {
|
||||
({ createDuckDuckGoWebSearchProvider } = await import("./ddg-search-provider.js"));
|
||||
|
||||
@@ -158,9 +158,9 @@ function mergeVoiceSettingsOverride(
|
||||
next: Record<string, unknown>,
|
||||
): SpeechProviderOverrides {
|
||||
return {
|
||||
...(ctx.currentOverrides ?? {}),
|
||||
...ctx.currentOverrides,
|
||||
voiceSettings: {
|
||||
...(asObject(ctx.currentOverrides?.voiceSettings) ?? {}),
|
||||
...asObject(ctx.currentOverrides?.voiceSettings),
|
||||
...next,
|
||||
},
|
||||
};
|
||||
@@ -181,7 +181,7 @@ function parseDirectiveToken(ctx: SpeechDirectiveTokenParseContext) {
|
||||
}
|
||||
return {
|
||||
handled: true,
|
||||
overrides: { ...(ctx.currentOverrides ?? {}), voiceId: ctx.value },
|
||||
overrides: { ...ctx.currentOverrides, voiceId: ctx.value },
|
||||
};
|
||||
case "model":
|
||||
case "modelid":
|
||||
@@ -193,7 +193,7 @@ function parseDirectiveToken(ctx: SpeechDirectiveTokenParseContext) {
|
||||
}
|
||||
return {
|
||||
handled: true,
|
||||
overrides: { ...(ctx.currentOverrides ?? {}), modelId: ctx.value },
|
||||
overrides: { ...ctx.currentOverrides, modelId: ctx.value },
|
||||
};
|
||||
case "stability": {
|
||||
if (!ctx.policy.allowVoiceSettings) {
|
||||
@@ -269,7 +269,7 @@ function parseDirectiveToken(ctx: SpeechDirectiveTokenParseContext) {
|
||||
return {
|
||||
handled: true,
|
||||
overrides: {
|
||||
...(ctx.currentOverrides ?? {}),
|
||||
...ctx.currentOverrides,
|
||||
applyTextNormalization: normalizeApplyTextNormalization(ctx.value),
|
||||
},
|
||||
};
|
||||
@@ -282,7 +282,7 @@ function parseDirectiveToken(ctx: SpeechDirectiveTokenParseContext) {
|
||||
return {
|
||||
handled: true,
|
||||
overrides: {
|
||||
...(ctx.currentOverrides ?? {}),
|
||||
...ctx.currentOverrides,
|
||||
languageCode: normalizeLanguageCode(ctx.value),
|
||||
},
|
||||
};
|
||||
@@ -293,7 +293,7 @@ function parseDirectiveToken(ctx: SpeechDirectiveTokenParseContext) {
|
||||
return {
|
||||
handled: true,
|
||||
overrides: {
|
||||
...(ctx.currentOverrides ?? {}),
|
||||
...ctx.currentOverrides,
|
||||
seed: normalizeSeed(Number.parseInt(ctx.value, 10)),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import plugin from "../index.js";
|
||||
import { __testing, createExaWebSearchProvider } from "./exa-web-search-provider.js";
|
||||
|
||||
describe("exa web search provider", () => {
|
||||
|
||||
@@ -187,11 +187,9 @@ function parseExaContents(
|
||||
if ("maxCharacters" in obj && parsePositiveInteger(obj.maxCharacters) === undefined) {
|
||||
return invalidContentsPayload("contents.text.maxCharacters must be a positive integer.");
|
||||
}
|
||||
return {
|
||||
...(parsePositiveInteger(obj.maxCharacters)
|
||||
? { maxCharacters: parsePositiveInteger(obj.maxCharacters) }
|
||||
: {}),
|
||||
};
|
||||
return parsePositiveInteger(obj.maxCharacters)
|
||||
? { maxCharacters: parsePositiveInteger(obj.maxCharacters) }
|
||||
: {};
|
||||
};
|
||||
|
||||
const parseHighlights = (
|
||||
@@ -457,7 +455,7 @@ function createExaToolDefinition(
|
||||
"Search the web using Exa AI. Supports neural or keyword search, publication date filters, and optional highlights or text extraction.",
|
||||
parameters: createExaSchema(),
|
||||
execute: async (args) => {
|
||||
const params = args as Record<string, unknown>;
|
||||
const params = args;
|
||||
const exaConfig = resolveExaConfig(searchConfig);
|
||||
const apiKey = resolveExaApiKey(exaConfig);
|
||||
if (!apiKey) {
|
||||
|
||||
@@ -16,7 +16,7 @@ import type {
|
||||
} from "./types.js";
|
||||
|
||||
const {
|
||||
listConfiguredAccountIds,
|
||||
_listConfiguredAccountIds,
|
||||
listAccountIds: listFeishuAccountIds,
|
||||
resolveDefaultAccountId,
|
||||
} = createAccountListHelpers("feishu", {
|
||||
|
||||
@@ -279,7 +279,7 @@ async function cleanupNewBitable(
|
||||
});
|
||||
cleanedFields++;
|
||||
} catch (err) {
|
||||
logger.debug(`Failed to rename primary field: ${err}`);
|
||||
logger.debug(`Failed to rename primary field: ${String(err)}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,7 +300,7 @@ async function cleanupNewBitable(
|
||||
});
|
||||
cleanedFields++;
|
||||
} catch (err) {
|
||||
logger.debug(`Failed to delete default field ${field.field_name}: ${err}`);
|
||||
logger.debug(`Failed to delete default field ${field.field_name}: ${String(err)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -334,7 +334,7 @@ async function cleanupNewBitable(
|
||||
});
|
||||
cleanedRows++;
|
||||
} catch (err) {
|
||||
logger.debug(`Failed to delete empty row ${recordId}: ${err}`);
|
||||
logger.debug(`Failed to delete empty row ${recordId}: ${String(err)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -381,7 +381,7 @@ async function createApp(
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
log.debug(`Cleanup failed (non-critical): ${err}`);
|
||||
log.debug(`Cleanup failed (non-critical): ${String(err)}`);
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -2,15 +2,9 @@ import type * as ConversationRuntime from "openclaw/plugin-sdk/conversation-runt
|
||||
import type { ResolvedAgentRoute } from "openclaw/plugin-sdk/routing";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createRuntimeEnv } from "../../../test/helpers/plugins/runtime-env.js";
|
||||
import type { ClawdbotConfig, PluginRuntime, RuntimeEnv } from "../runtime-api.js";
|
||||
import type { ClawdbotConfig, PluginRuntime } from "../runtime-api.js";
|
||||
import type { FeishuMessageEvent } from "./bot.js";
|
||||
import {
|
||||
buildBroadcastSessionKey,
|
||||
buildFeishuAgentBody,
|
||||
handleFeishuMessage,
|
||||
resolveBroadcastAgents,
|
||||
toMessageResourceType,
|
||||
} from "./bot.js";
|
||||
import { handleFeishuMessage } from "./bot.js";
|
||||
import { setFeishuRuntime } from "./runtime.js";
|
||||
|
||||
type ConfiguredBindingRoute = ReturnType<typeof ConversationRuntime.resolveConfiguredBindingRoute>;
|
||||
@@ -166,7 +160,7 @@ function buildDefaultResolveRoute(): ResolvedAgentRoute {
|
||||
};
|
||||
}
|
||||
|
||||
function createUnboundConfiguredRoute(
|
||||
function _createUnboundConfiguredRoute(
|
||||
route: NonNullable<ConfiguredBindingRoute>["route"],
|
||||
): ConfiguredBindingRoute {
|
||||
return { bindingResolution: null, route };
|
||||
@@ -202,7 +196,7 @@ function createFeishuBotRuntime(overrides: DeepPartial<PluginRuntime> = {}): Plu
|
||||
upsertPairingRequest: vi.fn(),
|
||||
buildPairingReply: vi.fn(),
|
||||
},
|
||||
...(overrides.channel ?? {}),
|
||||
...overrides.channel,
|
||||
},
|
||||
...(overrides.system ? { system: overrides.system as PluginRuntime["system"] } : {}),
|
||||
...(overrides.media ? { media: overrides.media as PluginRuntime["media"] } : {}),
|
||||
|
||||
@@ -111,9 +111,13 @@ export type FeishuBotAddedEvent = {
|
||||
// Returns null if no broadcast config exists or the peer is not in the broadcast list.
|
||||
export function resolveBroadcastAgents(cfg: ClawdbotConfig, peerId: string): string[] | null {
|
||||
const broadcast = (cfg as Record<string, unknown>).broadcast;
|
||||
if (!broadcast || typeof broadcast !== "object") return null;
|
||||
if (!broadcast || typeof broadcast !== "object") {
|
||||
return null;
|
||||
}
|
||||
const agents = (broadcast as Record<string, unknown>)[peerId];
|
||||
if (!Array.isArray(agents) || agents.length === 0) return null;
|
||||
if (!Array.isArray(agents) || agents.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return agents as string[];
|
||||
}
|
||||
|
||||
@@ -383,7 +387,9 @@ export async function handleFeishuMessage(params: {
|
||||
senderId: ctx.senderOpenId,
|
||||
log,
|
||||
});
|
||||
if (senderResult.name) ctx = { ...ctx, senderName: senderResult.name };
|
||||
if (senderResult.name) {
|
||||
ctx = { ...ctx, senderName: senderResult.name };
|
||||
}
|
||||
|
||||
// Track permission error to inform agent later (with cooldown to avoid repetition)
|
||||
if (senderResult.permissionError) {
|
||||
@@ -1092,9 +1098,10 @@ export async function handleFeishuMessage(params: {
|
||||
}
|
||||
|
||||
// --- Broadcast dispatch: send message to all configured agents ---
|
||||
const strategy =
|
||||
((cfg as Record<string, unknown>).broadcast as Record<string, unknown> | undefined)
|
||||
?.strategy || "parallel";
|
||||
const rawStrategy = (
|
||||
(cfg as Record<string, unknown>).broadcast as Record<string, unknown> | undefined
|
||||
)?.strategy;
|
||||
const strategy = rawStrategy === "sequential" ? "sequential" : "parallel";
|
||||
const activeAgentId =
|
||||
ctx.mentionedBot || !requireMention ? normalizeAgentId(route.agentId) : null;
|
||||
const agentIds = (cfg.agents?.list ?? []).map((a: { id: string }) => normalizeAgentId(a.id));
|
||||
|
||||
@@ -25,14 +25,20 @@ import { createRuntimeOutboundDelegates } from "openclaw/plugin-sdk/outbound-run
|
||||
import { createComputedAccountStatusAdapter } from "openclaw/plugin-sdk/status-helpers";
|
||||
import {
|
||||
inspectFeishuCredentials,
|
||||
listEnabledFeishuAccounts,
|
||||
listFeishuAccountIds,
|
||||
resolveDefaultFeishuAccountId,
|
||||
resolveFeishuAccount,
|
||||
resolveFeishuRuntimeAccount,
|
||||
listFeishuAccountIds,
|
||||
listEnabledFeishuAccounts,
|
||||
resolveDefaultFeishuAccountId,
|
||||
} from "./accounts.js";
|
||||
import { feishuApprovalAuth } from "./approval-auth.js";
|
||||
import { FEISHU_CARD_INTERACTION_VERSION } from "./card-interaction.js";
|
||||
import type {
|
||||
ChannelMessageActionName,
|
||||
ChannelMeta,
|
||||
ChannelPlugin,
|
||||
ClawdbotConfig,
|
||||
} from "./channel-runtime-api.js";
|
||||
import {
|
||||
buildChannelConfigSchema,
|
||||
buildProbeChannelStatusSummary,
|
||||
@@ -42,35 +48,25 @@ import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
PAIRING_APPROVED_MESSAGE,
|
||||
} from "./channel-runtime-api.js";
|
||||
import type {
|
||||
ChannelMessageActionName,
|
||||
ChannelMeta,
|
||||
ChannelPlugin,
|
||||
ClawdbotConfig,
|
||||
} from "./channel-runtime-api.js";
|
||||
import { createFeishuClient } from "./client.js";
|
||||
import { FeishuConfigSchema } from "./config-schema.js";
|
||||
import {
|
||||
buildFeishuModelOverrideParentCandidates,
|
||||
buildFeishuConversationId,
|
||||
buildFeishuModelOverrideParentCandidates,
|
||||
parseFeishuConversationId,
|
||||
parseFeishuDirectConversationId,
|
||||
parseFeishuTargetId,
|
||||
} from "./conversation-id.js";
|
||||
import { listFeishuDirectoryPeers, listFeishuDirectoryGroups } from "./directory.static.js";
|
||||
import { listFeishuDirectoryGroups, listFeishuDirectoryPeers } from "./directory.static.js";
|
||||
import { messageActionTargetAliases } from "./message-action-contract.js";
|
||||
import { resolveFeishuGroupToolPolicy } from "./policy.js";
|
||||
import { getFeishuRuntime } from "./runtime.js";
|
||||
import { collectRuntimeConfigAssignments, secretTargetRegistryEntries } from "./secret-contract.js";
|
||||
import { collectFeishuSecurityAuditFindings } from "./security-audit.js";
|
||||
import {
|
||||
resolveFeishuParentConversationCandidates,
|
||||
resolveFeishuSessionConversation,
|
||||
} from "./session-conversation.js";
|
||||
import { resolveFeishuSessionConversation } from "./session-conversation.js";
|
||||
import { resolveFeishuOutboundSessionRoute } from "./session-route.js";
|
||||
import { feishuSetupAdapter } from "./setup-core.js";
|
||||
import { feishuSetupWizard } from "./setup-surface.js";
|
||||
import { normalizeFeishuTarget, looksLikeFeishuId, formatFeishuTarget } from "./targets.js";
|
||||
import { looksLikeFeishuId, normalizeFeishuTarget } from "./targets.js";
|
||||
import type { FeishuConfig, FeishuProbeResult, ResolvedFeishuAccount } from "./types.js";
|
||||
|
||||
function readFeishuMediaParam(params: Record<string, unknown>): string | undefined {
|
||||
@@ -231,8 +227,7 @@ function setFeishuNamedAccountEnabled(
|
||||
|
||||
const feishuConfigAdapter = createHybridChannelConfigAdapter<
|
||||
ResolvedFeishuAccount,
|
||||
ResolvedFeishuAccount,
|
||||
ClawdbotConfig
|
||||
ResolvedFeishuAccount
|
||||
>({
|
||||
sectionKey: "feishu",
|
||||
listAccountIds: listFeishuAccountIds,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { FeishuConfigSchema } from "./config-schema.js";
|
||||
import type { FeishuConfig, ResolvedFeishuAccount } from "./types.js";
|
||||
import type { ResolvedFeishuAccount } from "./types.js";
|
||||
|
||||
type CreateFeishuClient = typeof import("./client.js").createFeishuClient;
|
||||
type CreateFeishuWSClient = typeof import("./client.js").createFeishuWSClient;
|
||||
|
||||
@@ -42,7 +42,9 @@ function getWsProxyAgent(): HttpsProxyAgent<string> | undefined {
|
||||
process.env.HTTPS_PROXY ||
|
||||
process.env.http_proxy ||
|
||||
process.env.HTTP_PROXY;
|
||||
if (!proxyUrl) return undefined;
|
||||
if (!proxyUrl) {
|
||||
return undefined;
|
||||
}
|
||||
return new httpsProxyAgentCtor(proxyUrl);
|
||||
}
|
||||
|
||||
|
||||
@@ -48,11 +48,15 @@ function collectDescendants(
|
||||
const visited = new Set<string>();
|
||||
|
||||
function collect(blockId: string) {
|
||||
if (visited.has(blockId)) return;
|
||||
if (visited.has(blockId)) {
|
||||
return;
|
||||
}
|
||||
visited.add(blockId);
|
||||
|
||||
const block = blockMap.get(blockId);
|
||||
if (!block) return;
|
||||
if (!block) {
|
||||
return;
|
||||
}
|
||||
|
||||
result.push(block);
|
||||
|
||||
|
||||
@@ -114,13 +114,13 @@ export function calculateAdaptiveColumnWidths(
|
||||
// Calculate weighted length (CJK chars count as 2)
|
||||
// CJK (Chinese/Japanese/Korean) characters render ~2x wider than ASCII
|
||||
function getWeightedLength(text: string): number {
|
||||
return [...text].reduce((sum, char) => {
|
||||
return Array.from(text).reduce((sum, char) => {
|
||||
return sum + (char.charCodeAt(0) > 255 ? 2 : 1);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
// Find max content length per column
|
||||
const maxLengths: number[] = new Array(column_size).fill(0);
|
||||
const maxLengths = Array.from({ length: column_size }, () => 0);
|
||||
|
||||
for (let row = 0; row < row_size; row++) {
|
||||
for (let col = 0; col < column_size; col++) {
|
||||
@@ -143,7 +143,7 @@ export function calculateAdaptiveColumnWidths(
|
||||
MIN_COLUMN_WIDTH,
|
||||
Math.min(MAX_COLUMN_WIDTH, Math.floor(totalWidth / column_size)),
|
||||
);
|
||||
return new Array(column_size).fill(equalWidth);
|
||||
return Array.from({ length: column_size }, () => equalWidth);
|
||||
}
|
||||
|
||||
// Calculate proportional widths
|
||||
@@ -160,11 +160,15 @@ export function calculateAdaptiveColumnWidths(
|
||||
while (remaining > 0) {
|
||||
// Find columns that can still grow (not at max)
|
||||
const growable = widths.map((w, i) => (w < MAX_COLUMN_WIDTH ? i : -1)).filter((i) => i >= 0);
|
||||
if (growable.length === 0) break;
|
||||
if (growable.length === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Distribute evenly among growable columns
|
||||
const perColumn = Math.floor(remaining / growable.length);
|
||||
if (perColumn === 0) break;
|
||||
if (perColumn === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (const i of growable) {
|
||||
const add = Math.min(perColumn, MAX_COLUMN_WIDTH - widths[i]);
|
||||
|
||||
@@ -155,7 +155,7 @@ describe("feishu_doc image fetch hardening", () => {
|
||||
registerFeishuDocTools(harness.api);
|
||||
const tool = harness.resolveTool("feishu_doc", context);
|
||||
expect(tool).toBeDefined();
|
||||
return tool as ToolLike;
|
||||
return tool;
|
||||
}
|
||||
|
||||
async function executeFeishuDocTool(
|
||||
|
||||
@@ -205,7 +205,7 @@ function normalizeConvertedBlockTree(
|
||||
const parentId = typeof block?.parent_id === "string" ? block.parent_id : "";
|
||||
return !childIds.has(blockId) && (!parentId || !byId.has(parentId));
|
||||
})
|
||||
.sort(
|
||||
.toSorted(
|
||||
(a, b) =>
|
||||
(originalOrder.get(a.block_id ?? "__missing__") ?? 0) -
|
||||
(originalOrder.get(b.block_id ?? "__missing__") ?? 0),
|
||||
@@ -396,7 +396,7 @@ async function chunkedConvertMarkdown(client: Lark.Client, markdown: string) {
|
||||
}
|
||||
|
||||
/** Insert blocks in batches of MAX_BLOCKS_PER_INSERT to avoid API 400 errors */
|
||||
async function chunkedInsertBlocks(
|
||||
async function _chunkedInsertBlocks(
|
||||
client: Lark.Client,
|
||||
docToken: string,
|
||||
blocks: FeishuDocxBlock[],
|
||||
@@ -894,7 +894,7 @@ async function createDoc(
|
||||
}
|
||||
const shouldGrantToRequester = options?.grantToRequester !== false;
|
||||
const requesterOpenId = options?.requesterOpenId?.trim();
|
||||
const requesterPermType: "edit" = "edit";
|
||||
const requesterPermType = "edit" as const;
|
||||
|
||||
let requesterPermissionAdded = false;
|
||||
let requesterPermissionSkippedReason: string | undefined;
|
||||
@@ -1009,7 +1009,9 @@ async function insertDoc(
|
||||
const blockInfo = await client.docx.documentBlock.get({
|
||||
path: { document_id: docToken, block_id: afterBlockId },
|
||||
});
|
||||
if (blockInfo.code !== 0) throw new Error(blockInfo.msg);
|
||||
if (blockInfo.code !== 0) {
|
||||
throw new Error(blockInfo.msg);
|
||||
}
|
||||
|
||||
const parentId = blockInfo.data?.block?.parent_id ?? docToken;
|
||||
|
||||
@@ -1023,7 +1025,9 @@ async function insertDoc(
|
||||
path: { document_id: docToken, block_id: parentId },
|
||||
params: pageToken ? { page_token: pageToken } : {},
|
||||
});
|
||||
if (childrenRes.code !== 0) throw new Error(childrenRes.msg);
|
||||
if (childrenRes.code !== 0) {
|
||||
throw new Error(childrenRes.msg);
|
||||
}
|
||||
items.push(...(childrenRes.data?.items ?? []));
|
||||
pageToken = childrenRes.data?.page_token ?? undefined;
|
||||
} while (pageToken);
|
||||
@@ -1039,7 +1043,9 @@ async function insertDoc(
|
||||
|
||||
logger?.info?.("feishu_doc: Converting markdown...");
|
||||
const { blocks, firstLevelBlockIds } = await chunkedConvertMarkdown(client, markdown);
|
||||
if (blocks.length === 0) throw new Error("Content is empty");
|
||||
if (blocks.length === 0) {
|
||||
throw new Error("Content is empty");
|
||||
}
|
||||
const { orderedBlocks, rootIds } = normalizeConvertedBlockTree(blocks, firstLevelBlockIds);
|
||||
|
||||
logger?.info?.(
|
||||
@@ -1144,8 +1150,8 @@ async function writeTableCells(
|
||||
}
|
||||
|
||||
const tableData = tableBlock.table;
|
||||
const rows = tableData?.property?.row_size as number | undefined;
|
||||
const cols = tableData?.property?.column_size as number | undefined;
|
||||
const rows = tableData?.property?.row_size;
|
||||
const cols = tableData?.property?.column_size;
|
||||
const cellIds = tableData?.cells ?? [];
|
||||
|
||||
if (!rows || !cols || !cellIds.length) {
|
||||
@@ -1163,7 +1169,9 @@ async function writeTableCells(
|
||||
|
||||
for (let c = 0; c < writeCols; c++) {
|
||||
const cellId = cellIds[r * cols + c];
|
||||
if (!cellId) continue;
|
||||
if (!cellId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// table cell is a container block: clear existing children, then create text child blocks
|
||||
const childrenRes = await client.docx.documentBlockChildren.get({
|
||||
|
||||
@@ -334,12 +334,17 @@ function applyCommentFileTypeDefault<
|
||||
|
||||
function formatDriveApiError(error: unknown): string {
|
||||
if (!isRecord(error)) {
|
||||
return String(error);
|
||||
return typeof error === "string" ? error : JSON.stringify(error);
|
||||
}
|
||||
const response = isRecord(error.response) ? error.response : undefined;
|
||||
const responseData = isRecord(response?.data) ? response?.data : undefined;
|
||||
return JSON.stringify({
|
||||
message: typeof error.message === "string" ? error.message : String(error),
|
||||
message:
|
||||
typeof error.message === "string"
|
||||
? error.message
|
||||
: typeof error === "string"
|
||||
? error
|
||||
: JSON.stringify(error),
|
||||
code: readString(error.code),
|
||||
method: readString(isRecord(error.config) ? error.config.method : undefined),
|
||||
url: readString(isRecord(error.config) ? error.config.url : undefined),
|
||||
@@ -360,12 +365,17 @@ function extractDriveApiErrorMeta(error: unknown): {
|
||||
feishuLogId?: string;
|
||||
} {
|
||||
if (!isRecord(error)) {
|
||||
return { message: String(error) };
|
||||
return { message: typeof error === "string" ? error : JSON.stringify(error) };
|
||||
}
|
||||
const response = isRecord(error.response) ? error.response : undefined;
|
||||
const responseData = isRecord(response?.data) ? response?.data : undefined;
|
||||
return {
|
||||
message: typeof error.message === "string" ? error.message : String(error),
|
||||
message:
|
||||
typeof error.message === "string"
|
||||
? error.message
|
||||
: typeof error === "string"
|
||||
? error
|
||||
: JSON.stringify(error),
|
||||
httpStatus: typeof response?.status === "number" ? response.status : undefined,
|
||||
feishuCode:
|
||||
typeof responseData?.code === "number" ? responseData.code : readString(responseData?.code),
|
||||
@@ -665,7 +675,7 @@ export async function replyComment(
|
||||
)}/replies`;
|
||||
const query = { file_type: params.file_type };
|
||||
try {
|
||||
const response = (await requestDriveApi<FeishuDriveApiResponse<Record<string, unknown>>>({
|
||||
const response = await requestDriveApi<FeishuDriveApiResponse<Record<string, unknown>>>({
|
||||
client,
|
||||
method: "POST",
|
||||
url,
|
||||
@@ -682,7 +692,7 @@ export async function replyComment(
|
||||
],
|
||||
},
|
||||
},
|
||||
})) as FeishuDriveApiResponse<Record<string, unknown>>;
|
||||
});
|
||||
if (response.code === 0) {
|
||||
return {
|
||||
success: true,
|
||||
|
||||
@@ -83,12 +83,12 @@ const {
|
||||
createFeishuReplyDispatcherMock,
|
||||
resolveBoundConversationMock,
|
||||
touchBindingMock,
|
||||
resolveAgentRouteMock,
|
||||
_resolveAgentRouteMock,
|
||||
resolveConfiguredBindingRouteMock,
|
||||
ensureConfiguredBindingRouteReadyMock,
|
||||
dispatchReplyFromConfigMock,
|
||||
withReplyDispatcherMock,
|
||||
finalizeInboundContextMock,
|
||||
_dispatchReplyFromConfigMock,
|
||||
_withReplyDispatcherMock,
|
||||
_finalizeInboundContextMock,
|
||||
getMessageFeishuMock,
|
||||
listFeishuThreadMessagesMock,
|
||||
sendMessageFeishuMock,
|
||||
|
||||
@@ -26,7 +26,7 @@ const {
|
||||
withReplyDispatcherMock,
|
||||
} = getFeishuLifecycleTestMocks();
|
||||
|
||||
let handlers: Record<string, (data: unknown) => Promise<void>> = {};
|
||||
let _handlers: Record<string, (data: unknown) => Promise<void>> = {};
|
||||
let lastRuntime: RuntimeEnv | null = null;
|
||||
const originalStateDir = process.env.OPENCLAW_STATE_DIR;
|
||||
const { cfg: lifecycleConfig, account: lifecycleAccount } = createFeishuLifecycleFixture({
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
import "./lifecycle.test-support.js";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createRuntimeEnv } from "../../../test/helpers/plugins/runtime-env.js";
|
||||
import type { ClawdbotConfig, RuntimeEnv } from "../runtime-api.js";
|
||||
import type { RuntimeEnv } from "../runtime-api.js";
|
||||
import "./lifecycle.test-support.js";
|
||||
import { getFeishuLifecycleTestMocks } from "./lifecycle.test-support.js";
|
||||
import {
|
||||
createFeishuLifecycleConfig,
|
||||
createFeishuLifecycleReplyDispatcher,
|
||||
createResolvedFeishuLifecycleAccount,
|
||||
expectFeishuReplyDispatcherSentFinalReplyOnce,
|
||||
expectFeishuReplyPipelineDedupedAcrossReplay,
|
||||
expectFeishuSingleEffectAcrossReplay,
|
||||
expectFeishuReplyDispatcherSentFinalReplyOnce,
|
||||
installFeishuLifecycleReplyRuntime,
|
||||
mockFeishuReplyOnceDispatch,
|
||||
restoreFeishuLifecycleStateDir,
|
||||
setFeishuLifecycleStateDir,
|
||||
setupFeishuLifecycleHandler,
|
||||
} from "./test-support/lifecycle-test-support.js";
|
||||
import type { ResolvedFeishuAccount } from "./types.js";
|
||||
|
||||
const {
|
||||
createEventDispatcherMock,
|
||||
@@ -30,7 +29,7 @@ const {
|
||||
withReplyDispatcherMock,
|
||||
} = getFeishuLifecycleTestMocks();
|
||||
|
||||
let handlers: Record<string, (data: unknown) => Promise<void>> = {};
|
||||
let _handlers: Record<string, (data: unknown) => Promise<void>> = {};
|
||||
let lastRuntime: RuntimeEnv | null = null;
|
||||
const originalStateDir = process.env.OPENCLAW_STATE_DIR;
|
||||
const lifecycleConfig = createFeishuLifecycleConfig({
|
||||
@@ -43,7 +42,7 @@ const lifecycleConfig = createFeishuLifecycleConfig({
|
||||
accountConfig: {
|
||||
dmPolicy: "open",
|
||||
},
|
||||
}) as ClawdbotConfig;
|
||||
});
|
||||
|
||||
const lifecycleAccount = createResolvedFeishuLifecycleAccount({
|
||||
accountId: "acct-menu",
|
||||
@@ -52,7 +51,7 @@ const lifecycleAccount = createResolvedFeishuLifecycleAccount({
|
||||
config: {
|
||||
dmPolicy: "open",
|
||||
},
|
||||
}) as ResolvedFeishuAccount;
|
||||
});
|
||||
|
||||
function createBotMenuEvent(params: { eventKey: string; timestamp: string }) {
|
||||
return {
|
||||
|
||||
@@ -23,7 +23,7 @@ const {
|
||||
finalizeInboundContextMock,
|
||||
resolveAgentRouteMock,
|
||||
resolveBoundConversationMock,
|
||||
sendMessageFeishuMock,
|
||||
_sendMessageFeishuMock,
|
||||
withReplyDispatcherMock,
|
||||
} = getFeishuLifecycleTestMocks();
|
||||
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
import "./lifecycle.test-support.js";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createRuntimeEnv } from "../../../test/helpers/plugins/runtime-env.js";
|
||||
import type { ClawdbotConfig, RuntimeEnv } from "../runtime-api.js";
|
||||
import type { RuntimeEnv } from "../runtime-api.js";
|
||||
import { resetProcessedFeishuCardActionTokensForTests } from "./card-action.js";
|
||||
import { createFeishuCardInteractionEnvelope } from "./card-interaction.js";
|
||||
import "./lifecycle.test-support.js";
|
||||
import { getFeishuLifecycleTestMocks } from "./lifecycle.test-support.js";
|
||||
import {
|
||||
createFeishuLifecycleConfig,
|
||||
createFeishuLifecycleReplyDispatcher,
|
||||
createResolvedFeishuLifecycleAccount,
|
||||
expectFeishuReplyDispatcherSentFinalReplyOnce,
|
||||
expectFeishuReplyPipelineDedupedAcrossReplay,
|
||||
expectFeishuReplyPipelineDedupedAfterPostSendFailure,
|
||||
expectFeishuReplyDispatcherSentFinalReplyOnce,
|
||||
installFeishuLifecycleReplyRuntime,
|
||||
mockFeishuReplyOnceDispatch,
|
||||
restoreFeishuLifecycleStateDir,
|
||||
setFeishuLifecycleStateDir,
|
||||
setupFeishuLifecycleHandler,
|
||||
} from "./test-support/lifecycle-test-support.js";
|
||||
import type { ResolvedFeishuAccount } from "./types.js";
|
||||
|
||||
const {
|
||||
createEventDispatcherMock,
|
||||
@@ -33,7 +32,7 @@ const {
|
||||
withReplyDispatcherMock,
|
||||
} = getFeishuLifecycleTestMocks();
|
||||
|
||||
let handlers: Record<string, (data: unknown) => Promise<void>> = {};
|
||||
let _handlers: Record<string, (data: unknown) => Promise<void>> = {};
|
||||
let lastRuntime: RuntimeEnv | null = null;
|
||||
const originalStateDir = process.env.OPENCLAW_STATE_DIR;
|
||||
const lifecycleConfig = createFeishuLifecycleConfig({
|
||||
@@ -46,7 +45,7 @@ const lifecycleConfig = createFeishuLifecycleConfig({
|
||||
accountConfig: {
|
||||
dmPolicy: "open",
|
||||
},
|
||||
}) as ClawdbotConfig;
|
||||
});
|
||||
|
||||
const lifecycleAccount = createResolvedFeishuLifecycleAccount({
|
||||
accountId: "acct-card",
|
||||
@@ -55,7 +54,7 @@ const lifecycleAccount = createResolvedFeishuLifecycleAccount({
|
||||
config: {
|
||||
dmPolicy: "open",
|
||||
},
|
||||
}) as ResolvedFeishuAccount;
|
||||
});
|
||||
|
||||
function createCardActionEvent(params: {
|
||||
token: string;
|
||||
|
||||
@@ -206,12 +206,17 @@ async function requestFeishuOpenApi<T>(params: {
|
||||
}): Promise<T | null> {
|
||||
const formatErrorDetails = (error: unknown): string => {
|
||||
if (!isRecord(error)) {
|
||||
return String(error);
|
||||
return typeof error === "string" ? error : JSON.stringify(error);
|
||||
}
|
||||
const response = isRecord(error.response) ? error.response : undefined;
|
||||
const responseData = isRecord(response?.data) ? response?.data : undefined;
|
||||
const details = {
|
||||
message: typeof error.message === "string" ? error.message : String(error),
|
||||
message:
|
||||
typeof error.message === "string"
|
||||
? error.message
|
||||
: typeof error === "string"
|
||||
? error
|
||||
: JSON.stringify(error),
|
||||
code: readString(error.code),
|
||||
method: readString(isRecord(error.config) ? error.config.method : undefined),
|
||||
url: readString(isRecord(error.config) ? error.config.url : undefined),
|
||||
@@ -230,7 +235,7 @@ async function requestFeishuOpenApi<T>(params: {
|
||||
url: params.url,
|
||||
data: params.data ?? {},
|
||||
timeout: params.timeoutMs,
|
||||
}) as Promise<T>,
|
||||
}),
|
||||
{ timeoutMs: params.timeoutMs },
|
||||
)
|
||||
.then((resolved) => (resolved.status === "resolved" ? resolved.value : null))
|
||||
|
||||
@@ -4,10 +4,7 @@ import {
|
||||
resolveInboundDebounceMs,
|
||||
} from "openclaw/plugin-sdk/reply-runtime";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
createNonExitingTypedRuntimeEnv,
|
||||
createRuntimeEnv,
|
||||
} from "../../../test/helpers/plugins/runtime-env.js";
|
||||
import { createNonExitingTypedRuntimeEnv } from "../../../test/helpers/plugins/runtime-env.js";
|
||||
import type { ClawdbotConfig, PluginRuntime, RuntimeEnv } from "../runtime-api.js";
|
||||
import { parseFeishuMessageEvent, type FeishuMessageEvent } from "./bot.js";
|
||||
import * as dedup from "./dedup.js";
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
import "./lifecycle.test-support.js";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createRuntimeEnv } from "../../../test/helpers/plugins/runtime-env.js";
|
||||
import type { ClawdbotConfig, RuntimeEnv } from "../runtime-api.js";
|
||||
import type { RuntimeEnv } from "../runtime-api.js";
|
||||
import "./lifecycle.test-support.js";
|
||||
import { getFeishuLifecycleTestMocks } from "./lifecycle.test-support.js";
|
||||
import {
|
||||
createFeishuLifecycleConfig,
|
||||
createFeishuLifecycleReplyDispatcher,
|
||||
createFeishuTextMessageEvent,
|
||||
createResolvedFeishuLifecycleAccount,
|
||||
expectFeishuReplyDispatcherSentFinalReplyOnce,
|
||||
expectFeishuReplyPipelineDedupedAcrossReplay,
|
||||
expectFeishuReplyPipelineDedupedAfterPostSendFailure,
|
||||
expectFeishuReplyDispatcherSentFinalReplyOnce,
|
||||
installFeishuLifecycleReplyRuntime,
|
||||
mockFeishuReplyOnceDispatch,
|
||||
restoreFeishuLifecycleStateDir,
|
||||
setFeishuLifecycleStateDir,
|
||||
setupFeishuLifecycleHandler,
|
||||
} from "./test-support/lifecycle-test-support.js";
|
||||
import type { ResolvedFeishuAccount } from "./types.js";
|
||||
|
||||
const {
|
||||
createEventDispatcherMock,
|
||||
@@ -30,7 +29,7 @@ const {
|
||||
withReplyDispatcherMock,
|
||||
} = getFeishuLifecycleTestMocks();
|
||||
|
||||
let handlers: Record<string, (data: unknown) => Promise<void>> = {};
|
||||
let _handlers: Record<string, (data: unknown) => Promise<void>> = {};
|
||||
let lastRuntime: RuntimeEnv | null = null;
|
||||
const originalStateDir = process.env.OPENCLAW_STATE_DIR;
|
||||
const lifecycleConfig = createFeishuLifecycleConfig({
|
||||
@@ -47,7 +46,7 @@ const lifecycleConfig = createFeishuLifecycleConfig({
|
||||
},
|
||||
},
|
||||
},
|
||||
}) as ClawdbotConfig;
|
||||
});
|
||||
|
||||
const lifecycleAccount = createResolvedFeishuLifecycleAccount({
|
||||
accountId: "acct-lifecycle",
|
||||
@@ -63,7 +62,7 @@ const lifecycleAccount = createResolvedFeishuLifecycleAccount({
|
||||
},
|
||||
},
|
||||
},
|
||||
}) as ResolvedFeishuAccount;
|
||||
});
|
||||
|
||||
async function setupLifecycleMonitor() {
|
||||
lastRuntime = createRuntimeEnv();
|
||||
|
||||
@@ -33,9 +33,9 @@ export type FeishuMonitorBotIdentity = {
|
||||
};
|
||||
|
||||
function isTimeoutErrorMessage(message: string | undefined): boolean {
|
||||
return message?.toLowerCase().includes("timeout") || message?.toLowerCase().includes("timed out")
|
||||
? true
|
||||
: false;
|
||||
return !!(
|
||||
message?.toLowerCase().includes("timeout") || message?.toLowerCase().includes("timed out")
|
||||
);
|
||||
}
|
||||
|
||||
function isAbortErrorMessage(message: string | undefined): boolean {
|
||||
|
||||
@@ -105,7 +105,9 @@ const feishuWebhookAnomalyTracker = createWebhookAnomalyTracker({
|
||||
});
|
||||
|
||||
function closeWsClient(client: Lark.WSClient | undefined): void {
|
||||
if (!client) return;
|
||||
if (!client) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
client.close();
|
||||
} catch {
|
||||
|
||||
@@ -102,7 +102,9 @@ export async function monitorWebSocket({
|
||||
let cleanedUp = false;
|
||||
|
||||
const cleanup = () => {
|
||||
if (cleanedUp) return;
|
||||
if (cleanedUp) {
|
||||
return;
|
||||
}
|
||||
cleanedUp = true;
|
||||
abortSignal?.removeEventListener("abort", handleAbort);
|
||||
try {
|
||||
@@ -131,7 +133,7 @@ export async function monitorWebSocket({
|
||||
abortSignal?.addEventListener("abort", handleAbort, { once: true });
|
||||
|
||||
try {
|
||||
wsClient.start({ eventDispatcher });
|
||||
void wsClient.start({ eventDispatcher });
|
||||
log(`feishu[${accountId}]: WebSocket client started`);
|
||||
} catch (err) {
|
||||
cleanup();
|
||||
|
||||
@@ -7,34 +7,47 @@ import { parseFeishuCommentTarget } from "./comment-target.js";
|
||||
import { replyComment } from "./drive.js";
|
||||
import { sendMediaFeishu } from "./media.js";
|
||||
import { chunkTextForOutbound, type ChannelOutboundAdapter } from "./outbound-runtime-api.js";
|
||||
import { getFeishuRuntime } from "./runtime.js";
|
||||
import { sendMarkdownCardFeishu, sendMessageFeishu, sendStructuredCardFeishu } from "./send.js";
|
||||
|
||||
function normalizePossibleLocalImagePath(text: string | undefined): string | null {
|
||||
const raw = text?.trim();
|
||||
if (!raw) return null;
|
||||
if (!raw) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Only auto-convert when the message is a pure path-like payload.
|
||||
// Avoid converting regular sentences that merely contain a path.
|
||||
const hasWhitespace = /\s/.test(raw);
|
||||
if (hasWhitespace) return null;
|
||||
if (hasWhitespace) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Ignore links/data URLs; those should stay in normal mediaUrl/text paths.
|
||||
if (/^(https?:\/\/|data:|file:\/\/)/i.test(raw)) return null;
|
||||
if (/^(https?:\/\/|data:|file:\/\/)/i.test(raw)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const ext = path.extname(raw).toLowerCase();
|
||||
const isImageExt = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".ico", ".tiff"].includes(
|
||||
ext,
|
||||
);
|
||||
if (!isImageExt) return null;
|
||||
if (!isImageExt) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!path.isAbsolute(raw)) return null;
|
||||
if (!fs.existsSync(raw)) return null;
|
||||
if (!path.isAbsolute(raw)) {
|
||||
return null;
|
||||
}
|
||||
if (!fs.existsSync(raw)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Fix race condition: wrap statSync in try-catch to handle file deletion
|
||||
// between existsSync and statSync
|
||||
try {
|
||||
if (!fs.statSync(raw).isFile()) return null;
|
||||
if (!fs.statSync(raw).isFile()) {
|
||||
return null;
|
||||
}
|
||||
} catch {
|
||||
// File may have been deleted or became inaccessible between checks
|
||||
return null;
|
||||
|
||||
@@ -143,8 +143,6 @@ export function resolveFeishuReplyPolicy(params: {
|
||||
? groupRequireMention
|
||||
: typeof resolvedCfg.requireMention === "boolean"
|
||||
? resolvedCfg.requireMention
|
||||
: params.groupPolicy === "open"
|
||||
? false
|
||||
: true,
|
||||
: params.groupPolicy !== "open",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { normalizeFeishuExternalKey } from "./external-keys.js";
|
||||
|
||||
const FALLBACK_POST_TEXT = "[Rich text message]";
|
||||
const MARKDOWN_SPECIAL_CHARS = /([\\`*_{}\[\]()#+\-!|>~])/g;
|
||||
const MARKDOWN_SPECIAL_CHARS = /([\\`*_{}[\]()#+\-!|>~])/g;
|
||||
|
||||
type PostParseResult = {
|
||||
textContent: string;
|
||||
|
||||
@@ -201,7 +201,9 @@ export function createFeishuReplyDispatcher(params: CreateFeishuReplyDispatcherP
|
||||
type StreamTextUpdateMode = "snapshot" | "delta";
|
||||
|
||||
const formatReasoningPrefix = (thinking: string): string => {
|
||||
if (!thinking) return "";
|
||||
if (!thinking) {
|
||||
return "";
|
||||
}
|
||||
const withoutLabel = thinking.replace(/^Reasoning:\n/, "");
|
||||
const plain = withoutLabel.replace(/^_(.*)_$/gm, "$1");
|
||||
const lines = plain.split("\n").map((line) => `> ${line}`);
|
||||
@@ -210,9 +212,15 @@ export function createFeishuReplyDispatcher(params: CreateFeishuReplyDispatcherP
|
||||
|
||||
const buildCombinedStreamText = (thinking: string, answer: string): string => {
|
||||
const parts: string[] = [];
|
||||
if (thinking) parts.push(formatReasoningPrefix(thinking));
|
||||
if (thinking && answer) parts.push("\n\n---\n\n");
|
||||
if (answer) parts.push(answer);
|
||||
if (thinking) {
|
||||
parts.push(formatReasoningPrefix(thinking));
|
||||
}
|
||||
if (thinking && answer) {
|
||||
parts.push("\n\n---\n\n");
|
||||
}
|
||||
if (answer) {
|
||||
parts.push(answer);
|
||||
}
|
||||
return parts.join("");
|
||||
};
|
||||
|
||||
@@ -250,7 +258,9 @@ export function createFeishuReplyDispatcher(params: CreateFeishuReplyDispatcherP
|
||||
};
|
||||
|
||||
const queueReasoningUpdate = (nextThinking: string) => {
|
||||
if (!nextThinking) return;
|
||||
if (!nextThinking) {
|
||||
return;
|
||||
}
|
||||
reasoningText = nextThinking;
|
||||
flushStreamingCardUpdate(buildCombinedStreamText(reasoningText, streamText));
|
||||
};
|
||||
|
||||
@@ -4,9 +4,8 @@ import type { ClawdbotConfig } from "../runtime-api.js";
|
||||
import { resolveFeishuRuntimeAccount } from "./accounts.js";
|
||||
import { createFeishuClient } from "./client.js";
|
||||
import type { MentionTarget } from "./mention.js";
|
||||
import { buildMentionedMessage, buildMentionedCardContent } from "./mention.js";
|
||||
import { buildMentionedCardContent, buildMentionedMessage } from "./mention.js";
|
||||
import { parsePostContent } from "./post.js";
|
||||
import { getFeishuRuntime } from "./runtime.js";
|
||||
import { assertFeishuMessageApiSuccess, toFeishuSendResult } from "./send-result.js";
|
||||
import { resolveFeishuSendTarget } from "./send-target.js";
|
||||
import type { FeishuChatType, FeishuMessageInfo, FeishuSendResult } from "./types.js";
|
||||
@@ -186,7 +185,7 @@ function parseInteractiveCardContent(parsed: unknown): string {
|
||||
const elements = Array.isArray(candidate.elements)
|
||||
? candidate.elements
|
||||
: Array.isArray(candidate.body?.elements)
|
||||
? candidate.body!.elements
|
||||
? candidate.body.elements
|
||||
: null;
|
||||
if (!elements) {
|
||||
return "[Interactive Card]";
|
||||
@@ -385,8 +384,12 @@ export async function listFeishuThreadMessages(params: {
|
||||
const results: FeishuThreadMessageInfo[] = [];
|
||||
|
||||
for (const item of items) {
|
||||
if (currentMessageId && item.message_id === currentMessageId) continue;
|
||||
if (rootMessageId && item.message_id === rootMessageId) continue;
|
||||
if (currentMessageId && item.message_id === currentMessageId) {
|
||||
continue;
|
||||
}
|
||||
if (rootMessageId && item.message_id === rootMessageId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const parsed = parseFeishuMessageItem(item);
|
||||
|
||||
@@ -399,7 +402,9 @@ export async function listFeishuThreadMessages(params: {
|
||||
createTime: parsed.createTime,
|
||||
});
|
||||
|
||||
if (results.length >= limit) break;
|
||||
if (results.length >= limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Restore chronological order (oldest first) since we fetched newest-first.
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
createPluginSetupWizardStatus,
|
||||
createTestWizardPrompter,
|
||||
runSetupWizardConfigure,
|
||||
runSetupWizardFinalize,
|
||||
type WizardPrompter,
|
||||
} from "../../../test/helpers/plugins/setup-wizard.js";
|
||||
|
||||
|
||||
@@ -14,12 +14,10 @@ import {
|
||||
} from "openclaw/plugin-sdk/setup";
|
||||
import {
|
||||
inspectFeishuCredentials,
|
||||
listFeishuAccountIds,
|
||||
resolveDefaultFeishuAccountId,
|
||||
resolveFeishuAccount,
|
||||
} from "./accounts.js";
|
||||
import { probeFeishu } from "./probe.js";
|
||||
import { feishuSetupAdapter } from "./setup-core.js";
|
||||
import type { FeishuAccountConfig, FeishuConfig } from "./types.js";
|
||||
|
||||
const channel = "feishu" as const;
|
||||
@@ -39,7 +37,7 @@ function getScopedFeishuConfig(cfg: OpenClawConfig, accountId: string): ScopedFe
|
||||
if (accountId === DEFAULT_ACCOUNT_ID) {
|
||||
return feishuCfg ?? {};
|
||||
}
|
||||
return (feishuCfg?.accounts?.[accountId] as FeishuAccountConfig | undefined) ?? {};
|
||||
return feishuCfg?.accounts?.[accountId] ?? {};
|
||||
}
|
||||
|
||||
function patchFeishuConfig(
|
||||
@@ -57,7 +55,7 @@ function patchFeishuConfig(
|
||||
});
|
||||
}
|
||||
const nextAccountPatch = {
|
||||
...((feishuCfg?.accounts?.[accountId] as Record<string, unknown> | undefined) ?? {}),
|
||||
...(feishuCfg?.accounts?.[accountId] as Record<string, unknown> | undefined),
|
||||
enabled: true,
|
||||
...patch,
|
||||
};
|
||||
@@ -67,7 +65,7 @@ function patchFeishuConfig(
|
||||
enabled: true,
|
||||
patch: {
|
||||
accounts: {
|
||||
...(feishuCfg?.accounts ?? {}),
|
||||
...feishuCfg?.accounts,
|
||||
[accountId]: nextAccountPatch,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
getRequiredHookHandler,
|
||||
registerHookHandlersForTest,
|
||||
@@ -6,8 +6,8 @@ import {
|
||||
import type { ClawdbotConfig, OpenClawPluginApi } from "../runtime-api.js";
|
||||
import { registerFeishuSubagentHooks } from "./subagent-hooks.js";
|
||||
import {
|
||||
__testing as threadBindingTesting,
|
||||
createFeishuThreadBindingManager,
|
||||
__testing as threadBindingTesting,
|
||||
} from "./thread-bindings.js";
|
||||
|
||||
const baseConfig: ClawdbotConfig = {
|
||||
|
||||
@@ -218,7 +218,7 @@ export function createFeishuThreadBindingManager(params: {
|
||||
},
|
||||
unbindBySessionKey: (targetSessionKey) => {
|
||||
const removed: FeishuThreadBindingRecord[] = [];
|
||||
for (const record of [...getState().bindingsByAccountConversation.values()]) {
|
||||
for (const record of getState().bindingsByAccountConversation.values()) {
|
||||
if (record.accountId !== accountId || record.targetSessionKey !== targetSessionKey) {
|
||||
continue;
|
||||
}
|
||||
@@ -230,7 +230,7 @@ export function createFeishuThreadBindingManager(params: {
|
||||
return removed;
|
||||
},
|
||||
stop: () => {
|
||||
for (const key of [...getState().bindingsByAccountConversation.keys()]) {
|
||||
for (const key of getState().bindingsByAccountConversation.keys()) {
|
||||
if (key.startsWith(`${accountId}:`)) {
|
||||
getState().bindingsByAccountConversation.delete(key);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,9 @@ type RegisteredTool = {
|
||||
};
|
||||
|
||||
function toToolList(value: AnyAgentTool | AnyAgentTool[] | null | undefined): AnyAgentTool[] {
|
||||
if (!value) return [];
|
||||
if (!value) {
|
||||
return [];
|
||||
}
|
||||
return Array.isArray(value) ? value : [value];
|
||||
}
|
||||
|
||||
|
||||
@@ -53,8 +53,8 @@ export function createFirecrawlWebFetchProvider(): WebFetchProviderPlugin {
|
||||
firecrawlEntry.config &&
|
||||
typeof firecrawlEntry.config === "object" &&
|
||||
!Array.isArray(firecrawlEntry.config)
|
||||
? (firecrawlEntry.config as Record<string, unknown>)
|
||||
: ((firecrawlEntry.config = {}), firecrawlEntry.config as Record<string, unknown>);
|
||||
? firecrawlEntry.config
|
||||
: ((firecrawlEntry.config = {}), firecrawlEntry.config);
|
||||
const webFetch =
|
||||
pluginConfig.webFetch &&
|
||||
typeof pluginConfig.webFetch === "object" &&
|
||||
|
||||
@@ -12,7 +12,7 @@ vi.mock("./register.runtime.js", () => ({
|
||||
|
||||
import plugin from "./index.js";
|
||||
|
||||
function registerProvider() {
|
||||
function _registerProvider() {
|
||||
return registerProviderWithPluginConfig({});
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
streamWithPayloadPatch,
|
||||
} from "openclaw/plugin-sdk/provider-stream-shared";
|
||||
|
||||
type StreamContext = Parameters<StreamFn>[1];
|
||||
type _StreamContext = Parameters<StreamFn>[1];
|
||||
|
||||
export function wrapCopilotAnthropicStream(baseStreamFn: StreamFn | undefined): StreamFn {
|
||||
const underlying = baseStreamFn ?? streamSimple;
|
||||
@@ -25,10 +25,10 @@ export function wrapCopilotAnthropicStream(baseStreamFn: StreamFn | undefined):
|
||||
...options,
|
||||
headers: {
|
||||
...buildCopilotDynamicHeaders({
|
||||
messages: context.messages as StreamContext["messages"],
|
||||
hasImages: hasCopilotVisionInput(context.messages as StreamContext["messages"]),
|
||||
messages: context.messages,
|
||||
hasImages: hasCopilotVisionInput(context.messages),
|
||||
}),
|
||||
...(options?.headers ?? {}),
|
||||
...options?.headers,
|
||||
},
|
||||
},
|
||||
applyAnthropicEphemeralCacheControlMarkers,
|
||||
|
||||
@@ -567,7 +567,7 @@ describe("loginGeminiCliOAuth", () => {
|
||||
if (Array.isArray(headers)) {
|
||||
return headers.find(([key]) => key.toLowerCase() === name.toLowerCase())?.[1];
|
||||
}
|
||||
return (headers as Record<string, string>)[name];
|
||||
return headers[name];
|
||||
}
|
||||
|
||||
function responseJson(body: unknown, status = 200): Response {
|
||||
|
||||
@@ -181,7 +181,7 @@ function createGeminiToolDefinition(
|
||||
"Search the web using Gemini with Google Search grounding. Returns AI-synthesized answers with citations from Google Search.",
|
||||
parameters: createGeminiSchema(),
|
||||
execute: async (args) => {
|
||||
const params = args as Record<string, unknown>;
|
||||
const params = args;
|
||||
const unsupportedResponse = buildUnsupportedSearchFilterResponse(params, "gemini");
|
||||
if (unsupportedResponse) {
|
||||
return unsupportedResponse;
|
||||
|
||||
@@ -44,11 +44,7 @@ function mergeGoogleChatAccountConfig(
|
||||
accountId,
|
||||
omitKeys: ["defaultAccount"],
|
||||
});
|
||||
const defaultAccountConfig =
|
||||
resolveAccountEntry(
|
||||
raw.accounts as Record<string, GoogleChatAccountConfig> | undefined,
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
) ?? {};
|
||||
const defaultAccountConfig = resolveAccountEntry(raw.accounts, DEFAULT_ACCOUNT_ID) ?? {};
|
||||
if (accountId === DEFAULT_ACCOUNT_ID) {
|
||||
return base;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
createDirectoryTestRuntime,
|
||||
expectDirectorySurface,
|
||||
} from "../../../test/helpers/plugins/directory.ts";
|
||||
import type { OpenClawConfig, PluginRuntime } from "../runtime-api.js";
|
||||
import type { OpenClawConfig } from "../runtime-api.js";
|
||||
|
||||
const uploadGoogleChatAttachmentMock = vi.hoisted(() => vi.fn());
|
||||
const sendGoogleChatMessageMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
@@ -7,8 +7,6 @@ import {
|
||||
import { createChatChannelPlugin } from "openclaw/plugin-sdk/channel-core";
|
||||
import {
|
||||
composeAccountWarningCollectors,
|
||||
composeWarningCollectors,
|
||||
createAllowlistProviderGroupPolicyWarningCollector,
|
||||
createAllowlistProviderOpenWarningCollector,
|
||||
} from "openclaw/plugin-sdk/channel-policy";
|
||||
import {
|
||||
@@ -32,18 +30,18 @@ import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
fetchRemoteMedia,
|
||||
GoogleChatConfigSchema,
|
||||
isGoogleChatSpaceTarget,
|
||||
isGoogleChatUserTarget,
|
||||
listGoogleChatAccountIds,
|
||||
loadOutboundMediaFromUrl,
|
||||
missingTargetError,
|
||||
normalizeGoogleChatTarget,
|
||||
PAIRING_APPROVED_MESSAGE,
|
||||
resolveChannelMediaMaxBytes,
|
||||
resolveDefaultGoogleChatAccountId,
|
||||
resolveGoogleChatAccount,
|
||||
resolveGoogleChatOutboundSpace,
|
||||
runPassiveAccountLifecycle,
|
||||
isGoogleChatSpaceTarget,
|
||||
isGoogleChatUserTarget,
|
||||
normalizeGoogleChatTarget,
|
||||
type ChannelMessageActionAdapter,
|
||||
type ChannelStatusIssue,
|
||||
type OpenClawConfig,
|
||||
@@ -51,7 +49,6 @@ import {
|
||||
} from "./channel.deps.runtime.js";
|
||||
import { collectGoogleChatMutableAllowlistWarnings } from "./doctor.js";
|
||||
import { resolveGoogleChatGroupRequireMention } from "./group-policy.js";
|
||||
import { getGoogleChatRuntime } from "./runtime.js";
|
||||
import { collectRuntimeConfigAssignments, secretTargetRegistryEntries } from "./secret-contract.js";
|
||||
import { googlechatSetupAdapter } from "./setup-core.js";
|
||||
import { googlechatSetupWizard } from "./setup-surface.js";
|
||||
|
||||
@@ -129,7 +129,7 @@ function warnDeprecatedUsersEmailEntries(logVerbose: (message: string) => void,
|
||||
}
|
||||
const key = deprecated
|
||||
.map((v) => v.toLowerCase())
|
||||
.sort()
|
||||
.toSorted()
|
||||
.join(",");
|
||||
if (warnedDeprecatedUsersEmailAllowFrom.has(key)) {
|
||||
return;
|
||||
@@ -152,7 +152,7 @@ function warnMutableGroupKeysConfigured(
|
||||
}
|
||||
const warningKey = mutableKeys
|
||||
.map((key) => key.toLowerCase())
|
||||
.sort()
|
||||
.toSorted()
|
||||
.join(",");
|
||||
if (warnedMutableGroupKeys.has(warningKey)) {
|
||||
return;
|
||||
|
||||
@@ -15,7 +15,13 @@ import type {
|
||||
} from "./types.js";
|
||||
|
||||
function extractBearerToken(header: unknown): string {
|
||||
const authHeader = Array.isArray(header) ? String(header[0] ?? "") : String(header ?? "");
|
||||
const authHeader = Array.isArray(header)
|
||||
? typeof header[0] === "string"
|
||||
? header[0]
|
||||
: ""
|
||||
: typeof header === "string"
|
||||
? header
|
||||
: "";
|
||||
return authHeader.toLowerCase().startsWith("bearer ")
|
||||
? authHeader.slice("bearer ".length).trim()
|
||||
: "";
|
||||
@@ -63,7 +69,10 @@ function parseGoogleChatInboundPayload(
|
||||
user: chat.user,
|
||||
eventTime: chat.eventTime,
|
||||
};
|
||||
addOnBearerToken = String(rawObj.authorizationEventObject?.systemIdToken ?? "").trim();
|
||||
addOnBearerToken =
|
||||
typeof rawObj.authorizationEventObject?.systemIdToken === "string"
|
||||
? rawObj.authorizationEventObject.systemIdToken.trim()
|
||||
: "";
|
||||
}
|
||||
|
||||
const event = eventPayload as GoogleChatEvent;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {
|
||||
createPatchedAccountSetupAdapter,
|
||||
createSetupInputPresenceValidator,
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
} from "openclaw/plugin-sdk/setup-runtime";
|
||||
|
||||
const channel = "googlechat" as const;
|
||||
|
||||
@@ -10,14 +10,8 @@ import {
|
||||
splitSetupEntries,
|
||||
type ChannelSetupDmPolicy,
|
||||
type ChannelSetupWizard,
|
||||
type OpenClawConfig,
|
||||
} from "openclaw/plugin-sdk/setup";
|
||||
import {
|
||||
listGoogleChatAccountIds,
|
||||
resolveDefaultGoogleChatAccountId,
|
||||
resolveGoogleChatAccount,
|
||||
} from "./accounts.js";
|
||||
import { googlechatSetupAdapter } from "./setup-core.js";
|
||||
import { resolveDefaultGoogleChatAccountId, resolveGoogleChatAccount } from "./accounts.js";
|
||||
|
||||
const channel = "googlechat" as const;
|
||||
const ENV_SERVICE_ACCOUNT = "GOOGLE_CHAT_SERVICE_ACCOUNT";
|
||||
@@ -41,7 +35,7 @@ const promptAllowFrom = createPromptParsedAllowFromForAccount({
|
||||
accountId,
|
||||
patch: {
|
||||
dm: {
|
||||
...(resolveGoogleChatAccount({ cfg, accountId }).config.dm ?? {}),
|
||||
...resolveGoogleChatAccount({ cfg, accountId }).config.dm,
|
||||
allowFrom,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -20,7 +20,7 @@ vi.mock("./onboard.js", () => ({
|
||||
|
||||
import plugin from "./index.js";
|
||||
|
||||
function registerProvider() {
|
||||
function _registerProvider() {
|
||||
return registerProviderWithPluginConfig({});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import type {
|
||||
ChannelSetupAdapter,
|
||||
ChannelSetupWizard,
|
||||
ChannelSetupWizardTextInput,
|
||||
} from "openclaw/plugin-sdk/setup-runtime";
|
||||
import {
|
||||
createCliPathTextInput,
|
||||
createDelegatedSetupWizardProxy,
|
||||
createDelegatedTextInputShouldPrompt,
|
||||
createPatchedAccountSetupAdapter,
|
||||
mergeAllowFromEntries,
|
||||
patchChannelConfigForAccount,
|
||||
parseSetupEntriesAllowingWildcard,
|
||||
patchChannelConfigForAccount,
|
||||
promptParsedAllowFromForAccount,
|
||||
setAccountAllowFromForChannel,
|
||||
setSetupChannelEnabled,
|
||||
type OpenClawConfig,
|
||||
type WizardPrompter,
|
||||
} from "openclaw/plugin-sdk/setup-runtime";
|
||||
import type {
|
||||
ChannelSetupAdapter,
|
||||
ChannelSetupWizard,
|
||||
ChannelSetupWizardTextInput,
|
||||
} from "openclaw/plugin-sdk/setup-runtime";
|
||||
import { formatDocsLink } from "openclaw/plugin-sdk/setup-tools";
|
||||
import { resolveDefaultIMessageAccountId, resolveIMessageAccount } from "./accounts.js";
|
||||
import { normalizeIMessageHandle } from "./targets.js";
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
import { createChatChannelPlugin } from "openclaw/plugin-sdk/channel-core";
|
||||
import {
|
||||
composeAccountWarningCollectors,
|
||||
composeWarningCollectors,
|
||||
createAllowlistProviderOpenWarningCollector,
|
||||
} from "openclaw/plugin-sdk/channel-policy";
|
||||
import {
|
||||
@@ -29,8 +28,8 @@ import {
|
||||
} from "./accounts.js";
|
||||
import {
|
||||
buildBaseChannelStatusSummary,
|
||||
createAccountStatusSink,
|
||||
chunkTextForOutbound,
|
||||
createAccountStatusSink,
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
PAIRING_APPROVED_MESSAGE,
|
||||
type ChannelPlugin,
|
||||
@@ -38,14 +37,13 @@ import {
|
||||
import { IrcChannelConfigSchema } from "./config-schema.js";
|
||||
import { collectIrcMutableAllowlistWarnings } from "./doctor.js";
|
||||
import {
|
||||
normalizeIrcMessagingTarget,
|
||||
looksLikeIrcTargetId,
|
||||
isChannelTarget,
|
||||
looksLikeIrcTargetId,
|
||||
normalizeIrcAllowEntry,
|
||||
normalizeIrcMessagingTarget,
|
||||
} from "./normalize.js";
|
||||
import { resolveIrcGroupMatch, resolveIrcRequireMention } from "./policy.js";
|
||||
import { probeIrc } from "./probe.js";
|
||||
import { getIrcRuntime } from "./runtime.js";
|
||||
import { collectRuntimeConfigAssignments, secretTargetRegistryEntries } from "./secret-contract.js";
|
||||
import { ircSetupAdapter } from "./setup-core.js";
|
||||
import { ircSetupWizard } from "./setup-surface.js";
|
||||
|
||||
@@ -83,7 +83,7 @@ function withTimeout<T>(promise: Promise<T>, timeoutMs: number, label: string):
|
||||
|
||||
function buildFallbackNick(nick: string): string {
|
||||
const normalized = nick.replace(/\s+/g, "");
|
||||
const safe = normalized.replace(/[^A-Za-z0-9_\-\[\]\\`^{}|]/g, "");
|
||||
const safe = normalized.replace(/[^A-Za-z0-9_\-[\]\\`^{}|]/g, "");
|
||||
const base = safe || "openclaw";
|
||||
const suffix = "_";
|
||||
const maxNickLen = 30;
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import {
|
||||
buildChannelConfigSchema,
|
||||
BlockStreamingCoalesceSchema,
|
||||
DmConfigSchema,
|
||||
DmPolicySchema,
|
||||
GroupPolicySchema,
|
||||
MarkdownConfigSchema,
|
||||
ReplyRuntimeConfigSchemaShape,
|
||||
ToolPolicySchema,
|
||||
buildChannelConfigSchema,
|
||||
requireOpenAllowFrom,
|
||||
} from "openclaw/plugin-sdk/channel-config-schema";
|
||||
import { z } from "openclaw/plugin-sdk/zod";
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import type { DmPolicy } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/routing";
|
||||
import type {
|
||||
ChannelSetupDmPolicy,
|
||||
ChannelSetupWizard,
|
||||
WizardPrompter,
|
||||
} from "openclaw/plugin-sdk/setup";
|
||||
import {
|
||||
createAllowFromSection,
|
||||
createPromptParsedAllowFromForAccount,
|
||||
createStandardChannelSetupStatus,
|
||||
formatDocsLink,
|
||||
setSetupChannelEnabled,
|
||||
} from "openclaw/plugin-sdk/setup";
|
||||
import type { ChannelSetupDmPolicy } from "openclaw/plugin-sdk/setup";
|
||||
import type { ChannelSetupWizard } from "openclaw/plugin-sdk/setup";
|
||||
import { formatDocsLink } from "openclaw/plugin-sdk/setup";
|
||||
import type { WizardPrompter } from "openclaw/plugin-sdk/setup";
|
||||
import { listIrcAccountIds, resolveDefaultIrcAccountId, resolveIrcAccount } from "./accounts.js";
|
||||
import { resolveDefaultIrcAccountId, resolveIrcAccount } from "./accounts.js";
|
||||
import {
|
||||
isChannelTarget,
|
||||
normalizeIrcAllowEntry,
|
||||
@@ -25,7 +26,7 @@ import {
|
||||
setIrcNickServ,
|
||||
updateIrcAccountConfig,
|
||||
} from "./setup-core.js";
|
||||
import type { CoreConfig, IrcAccountConfig, IrcNickServConfig } from "./types.js";
|
||||
import type { CoreConfig } from "./types.js";
|
||||
|
||||
const channel = "irc" as const;
|
||||
const USE_ENV_FLAG = "__ircUseEnv";
|
||||
@@ -258,7 +259,7 @@ export const ircSetupWizard: ChannelSetupWizard = {
|
||||
shouldPrompt: ({ credentialValues }) => credentialValues[USE_ENV_FLAG] !== "1",
|
||||
initialValue: ({ cfg, accountId, credentialValues }) => {
|
||||
const resolved = resolveIrcAccount({ cfg: cfg as CoreConfig, accountId });
|
||||
const tls = credentialValues[TLS_FLAG] === "0" ? false : true;
|
||||
const tls = credentialValues[TLS_FLAG] !== "0";
|
||||
const defaultPort = resolved.config.port ?? (tls ? 6697 : 6667);
|
||||
return String(defaultPort);
|
||||
},
|
||||
|
||||
@@ -275,7 +275,7 @@ describe("irc setup", () => {
|
||||
).toBeNull();
|
||||
|
||||
expect(
|
||||
applyAccountConfig!({
|
||||
applyAccountConfig({
|
||||
cfg: { channels: { irc: {} } },
|
||||
accountId: "default",
|
||||
input: {
|
||||
|
||||
@@ -28,7 +28,7 @@ function findExplicitProviderConfig(
|
||||
return isRecord(match?.[1]) ? match[1] : undefined;
|
||||
}
|
||||
|
||||
function buildKimiReplayPolicy() {
|
||||
function _buildKimiReplayPolicy() {
|
||||
return {
|
||||
preserveSignatures: false,
|
||||
};
|
||||
|
||||
@@ -197,6 +197,15 @@ type LineWebhookContext = Parameters<typeof import("./bot-handlers.js").handleLi
|
||||
|
||||
const createRuntime = () => ({ log: vi.fn(), error: vi.fn(), exit: vi.fn() });
|
||||
|
||||
function buildDefaultLineMessageContext() {
|
||||
return {
|
||||
ctxPayload: { From: "line:group:group-1" },
|
||||
replyToken: "reply-token",
|
||||
route: { agentId: "default" },
|
||||
isGroup: true,
|
||||
accountId: "default",
|
||||
};
|
||||
}
|
||||
function createReplayMessageEvent(params: {
|
||||
messageId: string;
|
||||
groupId: string;
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
type LineConfig,
|
||||
} from "./setup-runtime-api.js";
|
||||
|
||||
const channel = "line" as const;
|
||||
export function patchLineAccountConfig(params: {
|
||||
cfg: OpenClawConfig;
|
||||
accountId: string;
|
||||
|
||||
@@ -2,8 +2,7 @@ import crypto from "node:crypto";
|
||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { createMockIncomingRequest } from "../../../test/helpers/mock-incoming-request.js";
|
||||
import { createLineNodeWebhookHandler } from "./webhook-node.js";
|
||||
import { readLineWebhookRequestBody } from "./webhook-node.js";
|
||||
import { createLineNodeWebhookHandler, readLineWebhookRequestBody } from "./webhook-node.js";
|
||||
import { createLineWebhookMiddleware } from "./webhook.js";
|
||||
|
||||
const sign = (body: string, secret: string) =>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user