mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-28 02:12:07 +00:00
fix: repair latest-main ci gate
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
import { ToolPolicySchema } from "openclaw/plugin-sdk/agent-config-primitives";
|
||||
import {
|
||||
AllowFromListSchema,
|
||||
buildNestedDmConfigSchema,
|
||||
DmPolicySchema,
|
||||
GroupPolicySchema,
|
||||
MarkdownConfigSchema,
|
||||
} from "openclaw/plugin-sdk/channel-config-primitives";
|
||||
ToolPolicySchema,
|
||||
} from "openclaw/plugin-sdk/channel-config-schema";
|
||||
import { buildSecretInputSchema } from "openclaw/plugin-sdk/secret-input";
|
||||
import { z } from "openclaw/plugin-sdk/zod";
|
||||
|
||||
|
||||
7
extensions/mattermost/src/config-runtime.ts
Normal file
7
extensions/mattermost/src/config-runtime.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export {
|
||||
BlockStreamingCoalesceSchema,
|
||||
DmPolicySchema,
|
||||
GroupPolicySchema,
|
||||
MarkdownConfigSchema,
|
||||
requireOpenAllowFrom,
|
||||
} from "openclaw/plugin-sdk/channel-config-schema";
|
||||
@@ -1 +1,125 @@
|
||||
export { MattermostConfigSchema } from "./config-schema-core.js";
|
||||
import { z } from "openclaw/plugin-sdk/zod";
|
||||
import {
|
||||
BlockStreamingCoalesceSchema,
|
||||
DmPolicySchema,
|
||||
GroupPolicySchema,
|
||||
MarkdownConfigSchema,
|
||||
requireOpenAllowFrom,
|
||||
} from "./config-runtime.js";
|
||||
import { buildSecretInputSchema } from "./secret-input.js";
|
||||
|
||||
function requireMattermostOpenAllowFrom(params: {
|
||||
policy?: string;
|
||||
allowFrom?: Array<string | number>;
|
||||
ctx: z.RefinementCtx;
|
||||
}) {
|
||||
requireOpenAllowFrom({
|
||||
policy: params.policy,
|
||||
allowFrom: params.allowFrom,
|
||||
ctx: params.ctx,
|
||||
path: ["allowFrom"],
|
||||
message:
|
||||
'channels.mattermost.dmPolicy="open" requires channels.mattermost.allowFrom to include "*"',
|
||||
});
|
||||
}
|
||||
|
||||
const DmChannelRetrySchema = z
|
||||
.object({
|
||||
/** Maximum number of retry attempts for DM channel creation (default: 3) */
|
||||
maxRetries: z.number().int().min(0).max(10).optional(),
|
||||
/** Initial delay in milliseconds before first retry (default: 1000) */
|
||||
initialDelayMs: z.number().int().min(100).max(60000).optional(),
|
||||
/** Maximum delay in milliseconds between retries (default: 10000) */
|
||||
maxDelayMs: z.number().int().min(1000).max(60000).optional(),
|
||||
/** Timeout for each individual DM channel creation request in milliseconds (default: 30000) */
|
||||
timeoutMs: z.number().int().min(5000).max(120000).optional(),
|
||||
})
|
||||
.strict()
|
||||
.refine(
|
||||
(data) => {
|
||||
if (data.initialDelayMs !== undefined && data.maxDelayMs !== undefined) {
|
||||
return data.initialDelayMs <= data.maxDelayMs;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message: "initialDelayMs must be less than or equal to maxDelayMs",
|
||||
path: ["initialDelayMs"],
|
||||
},
|
||||
)
|
||||
.optional();
|
||||
|
||||
const MattermostSlashCommandsSchema = z
|
||||
.object({
|
||||
/** Enable native slash commands. "auto" resolves to false (opt-in). */
|
||||
native: z.union([z.boolean(), z.literal("auto")]).optional(),
|
||||
/** Also register skill-based commands. */
|
||||
nativeSkills: z.union([z.boolean(), z.literal("auto")]).optional(),
|
||||
/** Path for the callback endpoint on the gateway HTTP server. */
|
||||
callbackPath: z.string().optional(),
|
||||
/** Explicit callback URL (e.g. behind reverse proxy). */
|
||||
callbackUrl: z.string().optional(),
|
||||
})
|
||||
.strict()
|
||||
.optional();
|
||||
|
||||
const MattermostAccountSchemaBase = z
|
||||
.object({
|
||||
name: z.string().optional(),
|
||||
capabilities: z.array(z.string()).optional(),
|
||||
dangerouslyAllowNameMatching: z.boolean().optional(),
|
||||
markdown: MarkdownConfigSchema,
|
||||
enabled: z.boolean().optional(),
|
||||
configWrites: z.boolean().optional(),
|
||||
botToken: buildSecretInputSchema().optional(),
|
||||
baseUrl: z.string().optional(),
|
||||
chatmode: z.enum(["oncall", "onmessage", "onchar"]).optional(),
|
||||
oncharPrefixes: z.array(z.string()).optional(),
|
||||
requireMention: z.boolean().optional(),
|
||||
dmPolicy: DmPolicySchema.optional().default("pairing"),
|
||||
allowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
||||
groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
||||
groupPolicy: GroupPolicySchema.optional().default("allowlist"),
|
||||
textChunkLimit: z.number().int().positive().optional(),
|
||||
chunkMode: z.enum(["length", "newline"]).optional(),
|
||||
blockStreaming: z.boolean().optional(),
|
||||
blockStreamingCoalesce: BlockStreamingCoalesceSchema.optional(),
|
||||
replyToMode: z.enum(["off", "first", "all"]).optional(),
|
||||
responsePrefix: z.string().optional(),
|
||||
actions: z
|
||||
.object({
|
||||
reactions: z.boolean().optional(),
|
||||
})
|
||||
.optional(),
|
||||
commands: MattermostSlashCommandsSchema,
|
||||
interactions: z
|
||||
.object({
|
||||
callbackBaseUrl: z.string().optional(),
|
||||
allowedSourceIps: z.array(z.string()).optional(),
|
||||
})
|
||||
.optional(),
|
||||
/** Allow fetching from private/internal IP addresses (e.g. localhost). Required for self-hosted Mattermost on LAN/VPN. */
|
||||
allowPrivateNetwork: z.boolean().optional(),
|
||||
/** Retry configuration for DM channel creation */
|
||||
dmChannelRetry: DmChannelRetrySchema,
|
||||
})
|
||||
.strict();
|
||||
|
||||
const MattermostAccountSchema = MattermostAccountSchemaBase.superRefine((value, ctx) => {
|
||||
requireMattermostOpenAllowFrom({
|
||||
policy: value.dmPolicy,
|
||||
allowFrom: value.allowFrom,
|
||||
ctx,
|
||||
});
|
||||
});
|
||||
|
||||
export const MattermostConfigSchema = MattermostAccountSchemaBase.extend({
|
||||
accounts: z.record(z.string(), MattermostAccountSchema.optional()).optional(),
|
||||
defaultAccount: z.string().optional(),
|
||||
}).superRefine((value, ctx) => {
|
||||
requireMattermostOpenAllowFrom({
|
||||
policy: value.dmPolicy,
|
||||
allowFrom: value.allowFrom,
|
||||
ctx,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,33 +1,17 @@
|
||||
import {
|
||||
ReplyRuntimeConfigSchemaShape,
|
||||
ToolPolicySchema,
|
||||
} from "openclaw/plugin-sdk/agent-config-primitives";
|
||||
import {
|
||||
BlockStreamingCoalesceSchema,
|
||||
DmConfigSchema,
|
||||
DmPolicySchema,
|
||||
GroupPolicySchema,
|
||||
MarkdownConfigSchema,
|
||||
ReplyRuntimeConfigSchemaShape,
|
||||
ToolPolicySchema,
|
||||
requireOpenAllowFrom,
|
||||
} from "openclaw/plugin-sdk/channel-config-primitives";
|
||||
} from "openclaw/plugin-sdk/channel-config-schema";
|
||||
import { requireChannelOpenAllowFrom } from "openclaw/plugin-sdk/extension-shared";
|
||||
import { z } from "openclaw/plugin-sdk/zod";
|
||||
import { buildSecretInputSchema } from "./secret-input.js";
|
||||
|
||||
function requireNextcloudTalkOpenAllowFrom(params: {
|
||||
policy?: string;
|
||||
allowFrom?: string[];
|
||||
ctx: z.RefinementCtx;
|
||||
}) {
|
||||
requireOpenAllowFrom({
|
||||
policy: params.policy,
|
||||
allowFrom: params.allowFrom,
|
||||
ctx: params.ctx,
|
||||
path: ["allowFrom"],
|
||||
message:
|
||||
'channels.nextcloud-talk.dmPolicy="open" requires channels.nextcloud-talk.allowFrom to include "*"',
|
||||
});
|
||||
}
|
||||
|
||||
export const NextcloudTalkRoomSchema = z
|
||||
.object({
|
||||
requireMention: z.boolean().optional(),
|
||||
@@ -67,10 +51,12 @@ export const NextcloudTalkAccountSchemaBase = z
|
||||
|
||||
export const NextcloudTalkAccountSchema = NextcloudTalkAccountSchemaBase.superRefine(
|
||||
(value, ctx) => {
|
||||
requireNextcloudTalkOpenAllowFrom({
|
||||
requireChannelOpenAllowFrom({
|
||||
channel: "nextcloud-talk",
|
||||
policy: value.dmPolicy,
|
||||
allowFrom: value.allowFrom,
|
||||
ctx,
|
||||
requireOpenAllowFrom,
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -79,9 +65,11 @@ export const NextcloudTalkConfigSchema = NextcloudTalkAccountSchemaBase.extend({
|
||||
accounts: z.record(z.string(), NextcloudTalkAccountSchema.optional()).optional(),
|
||||
defaultAccount: z.string().optional(),
|
||||
}).superRefine((value, ctx) => {
|
||||
requireNextcloudTalkOpenAllowFrom({
|
||||
requireChannelOpenAllowFrom({
|
||||
channel: "nextcloud-talk",
|
||||
policy: value.dmPolicy,
|
||||
allowFrom: value.allowFrom,
|
||||
ctx,
|
||||
requireOpenAllowFrom,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
resolveMergedAccountConfig,
|
||||
type OpenClawConfig,
|
||||
} from "openclaw/plugin-sdk/account-resolution";
|
||||
import type { SignalAccountConfig } from "openclaw/plugin-sdk/signal-core";
|
||||
import type { SignalAccountConfig } from "./runtime-api.js";
|
||||
|
||||
export type ResolvedSignalAccount = {
|
||||
accountId: string;
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
DmPolicySchema,
|
||||
GroupPolicySchema,
|
||||
MarkdownConfigSchema,
|
||||
} from "openclaw/plugin-sdk/channel-config-primitives";
|
||||
} from "openclaw/plugin-sdk/channel-config-schema";
|
||||
import { z } from "openclaw/plugin-sdk/zod";
|
||||
import { buildSecretInputSchema } from "./secret-input.js";
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { ToolPolicySchema } from "openclaw/plugin-sdk/agent-config-primitives";
|
||||
import {
|
||||
AllowFromListSchema,
|
||||
buildCatchallMultiAccountChannelSchema,
|
||||
DmPolicySchema,
|
||||
GroupPolicySchema,
|
||||
MarkdownConfigSchema,
|
||||
} from "openclaw/plugin-sdk/channel-config-primitives";
|
||||
ToolPolicySchema,
|
||||
} from "openclaw/plugin-sdk/channel-config-schema";
|
||||
import { z } from "openclaw/plugin-sdk/zod";
|
||||
|
||||
const groupConfigSchema = z.object({
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { execFileSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { collectBundledPluginBuildEntries } from "./lib/bundled-plugin-build-entries.mjs";
|
||||
import { collectBundledPluginSources } from "./lib/bundled-plugin-source-utils.mjs";
|
||||
import { formatGeneratedModule } from "./lib/format-generated-module.mjs";
|
||||
import { writeGeneratedOutput } from "./lib/generated-output-utils.mjs";
|
||||
@@ -27,7 +26,8 @@ const DEFAULT_BUNDLED_CHANNEL_ENTRY_IDS = [
|
||||
];
|
||||
const MANIFEST_KEY = "openclaw";
|
||||
const FORMATTER_CWD = path.resolve(import.meta.dirname, "..");
|
||||
const RUNTIME_SIDECAR_PUBLIC_SURFACE_BASENAMES = new Set([
|
||||
const PUBLIC_SURFACE_SOURCE_EXTENSIONS = new Set([".ts", ".mts", ".js", ".mjs", ".cts", ".cjs"]);
|
||||
const RUNTIME_SIDECAR_ARTIFACTS = new Set([
|
||||
"helper-api.js",
|
||||
"light-runtime-api.js",
|
||||
"runtime-api.js",
|
||||
@@ -42,6 +42,53 @@ function rewriteEntryToBuiltPath(entry) {
|
||||
return normalized.replace(/\.[^.]+$/u, ".js");
|
||||
}
|
||||
|
||||
function isTopLevelPublicSurfaceSource(name) {
|
||||
if (!PUBLIC_SURFACE_SOURCE_EXTENSIONS.has(path.extname(name))) {
|
||||
return false;
|
||||
}
|
||||
if (name.startsWith(".")) {
|
||||
return false;
|
||||
}
|
||||
if (name.startsWith("test-")) {
|
||||
return false;
|
||||
}
|
||||
if (name.includes(".test-")) {
|
||||
return false;
|
||||
}
|
||||
if (name.endsWith(".d.ts")) {
|
||||
return false;
|
||||
}
|
||||
return !/(\.test|\.spec)(\.[cm]?[jt]s)$/u.test(name);
|
||||
}
|
||||
|
||||
function collectTopLevelPublicSurfaceArtifacts(params) {
|
||||
const excluded = new Set(
|
||||
[params.sourceEntry, params.setupEntry]
|
||||
.filter((entry) => typeof entry === "string" && entry.trim().length > 0)
|
||||
.map((entry) => path.basename(entry)),
|
||||
);
|
||||
const artifacts = fs
|
||||
.readdirSync(params.pluginDir, { withFileTypes: true })
|
||||
.filter((entry) => entry.isFile())
|
||||
.map((entry) => entry.name)
|
||||
.filter(isTopLevelPublicSurfaceSource)
|
||||
.filter((entry) => !excluded.has(entry))
|
||||
.map(rewriteEntryToBuiltPath)
|
||||
.filter((entry) => typeof entry === "string" && entry.length > 0)
|
||||
.toSorted((left, right) => left.localeCompare(right));
|
||||
return artifacts.length > 0 ? artifacts : undefined;
|
||||
}
|
||||
|
||||
function collectRuntimeSidecarArtifacts(publicSurfaceArtifacts) {
|
||||
if (!publicSurfaceArtifacts) {
|
||||
return undefined;
|
||||
}
|
||||
const runtimeSidecarArtifacts = publicSurfaceArtifacts.filter((artifact) =>
|
||||
RUNTIME_SIDECAR_ARTIFACTS.has(artifact),
|
||||
);
|
||||
return runtimeSidecarArtifacts.length > 0 ? runtimeSidecarArtifacts : undefined;
|
||||
}
|
||||
|
||||
function deriveIdHint({ filePath, manifestId, packageName, hasMultipleExtensions }) {
|
||||
const base = path.basename(filePath, path.extname(filePath));
|
||||
const normalizedManifestId = manifestId?.trim();
|
||||
@@ -140,8 +187,10 @@ function normalizePluginManifest(raw) {
|
||||
id: raw.id.trim(),
|
||||
configSchema: raw.configSchema,
|
||||
...(raw.enabledByDefault === true ? { enabledByDefault: true } : {}),
|
||||
...(normalizeStringList(raw.legacyPluginIds)
|
||||
? { legacyPluginIds: normalizeStringList(raw.legacyPluginIds) }
|
||||
...(typeof raw.kind === "string" ? { kind: raw.kind.trim() } : {}),
|
||||
...(normalizeStringList(raw.channels) ? { channels: normalizeStringList(raw.channels) } : {}),
|
||||
...(normalizeStringList(raw.providers)
|
||||
? { providers: normalizeStringList(raw.providers) }
|
||||
: {}),
|
||||
...(normalizeStringList(raw.autoEnableWhenConfiguredProviders)
|
||||
? {
|
||||
@@ -150,14 +199,12 @@ function normalizePluginManifest(raw) {
|
||||
),
|
||||
}
|
||||
: {}),
|
||||
...(typeof raw.kind === "string" ? { kind: raw.kind.trim() } : {}),
|
||||
...(normalizeStringList(raw.channels) ? { channels: normalizeStringList(raw.channels) } : {}),
|
||||
...(normalizeStringList(raw.providers)
|
||||
? { providers: normalizeStringList(raw.providers) }
|
||||
: {}),
|
||||
...(normalizeStringList(raw.cliBackends)
|
||||
? { cliBackends: normalizeStringList(raw.cliBackends) }
|
||||
: {}),
|
||||
...(normalizeStringList(raw.legacyPluginIds)
|
||||
? { legacyPluginIds: normalizeStringList(raw.legacyPluginIds) }
|
||||
: {}),
|
||||
...(normalizeObject(raw.providerAuthEnvVars)
|
||||
? { providerAuthEnvVars: raw.providerAuthEnvVars }
|
||||
: {}),
|
||||
@@ -196,10 +243,6 @@ function resolvePackageChannelMeta(packageJson) {
|
||||
|
||||
function resolveChannelConfigSchemaModulePath(rootDir) {
|
||||
const candidates = [
|
||||
path.join(rootDir, "src", "config-surface.ts"),
|
||||
path.join(rootDir, "src", "config-surface.js"),
|
||||
path.join(rootDir, "src", "config-surface.mts"),
|
||||
path.join(rootDir, "src", "config-surface.mjs"),
|
||||
path.join(rootDir, "src", "config-schema.ts"),
|
||||
path.join(rootDir, "src", "config-schema.js"),
|
||||
path.join(rootDir, "src", "config-schema.mts"),
|
||||
@@ -262,16 +305,28 @@ async function collectBundledChannelConfigsForSource({ source, manifest }) {
|
||||
return Object.keys(existingChannelConfigs).length > 0 ? existingChannelConfigs : undefined;
|
||||
}
|
||||
|
||||
const surfaceJson = execFileSync(
|
||||
process.execPath,
|
||||
["--import", "tsx", "scripts/load-channel-config-surface.ts", modulePath],
|
||||
{
|
||||
const runSurfaceLoader = (command, args) =>
|
||||
execFileSync(command, args, {
|
||||
// Run from the host repo so the generator always resolves its own loader/tooling,
|
||||
// even when inspecting a temporary or alternate repo root.
|
||||
cwd: FORMATTER_CWD,
|
||||
encoding: "utf8",
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
let surfaceJson;
|
||||
try {
|
||||
surfaceJson = runSurfaceLoader("bun", ["scripts/load-channel-config-surface.ts", modulePath]);
|
||||
} catch (error) {
|
||||
if (!error || typeof error !== "object" || error.code !== "ENOENT") {
|
||||
throw error;
|
||||
}
|
||||
surfaceJson = runSurfaceLoader(process.execPath, [
|
||||
"--import",
|
||||
"tsx",
|
||||
"scripts/load-channel-config-surface.ts",
|
||||
modulePath,
|
||||
]);
|
||||
}
|
||||
const surface = JSON.parse(surfaceJson);
|
||||
if (!surface?.schema) {
|
||||
return Object.keys(existingChannelConfigs).length > 0 ? existingChannelConfigs : undefined;
|
||||
@@ -332,25 +387,6 @@ function normalizeGeneratedImportPath(dirName, builtPath) {
|
||||
return `../../extensions/${dirName}/${String(builtPath).replace(/^\.\//u, "")}`;
|
||||
}
|
||||
|
||||
function normalizeEntryPath(entry) {
|
||||
return String(entry).replace(/^\.\//u, "");
|
||||
}
|
||||
|
||||
function isPublicSurfaceArtifactSourceEntry(entry) {
|
||||
const baseName = path.posix.basename(normalizeEntryPath(entry));
|
||||
if (baseName.startsWith("test-")) {
|
||||
return false;
|
||||
}
|
||||
if (baseName.includes(".test-")) {
|
||||
return false;
|
||||
}
|
||||
return !baseName.endsWith(".test.ts") && !baseName.endsWith(".test.js");
|
||||
}
|
||||
|
||||
function isRuntimeSidecarPublicSurfaceArtifact(artifact) {
|
||||
return RUNTIME_SIDECAR_PUBLIC_SURFACE_BASENAMES.has(path.posix.basename(String(artifact)));
|
||||
}
|
||||
|
||||
function resolveBundledChannelEntries(entries) {
|
||||
const orderById = new Map(DEFAULT_BUNDLED_CHANNEL_ENTRY_IDS.map((id, index) => [id, index]));
|
||||
return entries
|
||||
@@ -369,9 +405,6 @@ function resolveBundledChannelEntries(entries) {
|
||||
|
||||
export async function collectBundledPluginMetadata(params = {}) {
|
||||
const repoRoot = path.resolve(params.repoRoot ?? process.cwd());
|
||||
const buildEntriesById = new Map(
|
||||
collectBundledPluginBuildEntries({ cwd: repoRoot }).map((entry) => [entry.id, entry]),
|
||||
);
|
||||
const entries = [];
|
||||
for (const source of collectBundledPluginSources({ repoRoot, requirePackageJson: true })) {
|
||||
const manifest = normalizePluginManifest(source.manifest);
|
||||
@@ -401,27 +434,12 @@ export async function collectBundledPluginMetadata(params = {}) {
|
||||
built: rewriteEntryToBuiltPath(packageManifest.setupEntry.trim()),
|
||||
}
|
||||
: undefined;
|
||||
const publicSurfaceArtifacts = (() => {
|
||||
const buildEntry = buildEntriesById.get(source.dirName);
|
||||
if (!buildEntry) {
|
||||
return undefined;
|
||||
}
|
||||
const excludedEntries = new Set(
|
||||
[sourceEntry, setupEntry?.source]
|
||||
.filter((entry) => typeof entry === "string" && entry.trim().length > 0)
|
||||
.map(normalizeEntryPath),
|
||||
);
|
||||
const artifacts = buildEntry.sourceEntries
|
||||
.map(normalizeEntryPath)
|
||||
.filter((entry) => !excludedEntries.has(entry))
|
||||
.filter(isPublicSurfaceArtifactSourceEntry)
|
||||
.map(rewriteEntryToBuiltPath)
|
||||
.filter((entry) => typeof entry === "string" && entry.length > 0)
|
||||
.toSorted((left, right) => left.localeCompare(right));
|
||||
return artifacts.length > 0 ? artifacts : undefined;
|
||||
})();
|
||||
const runtimeSidecarArtifacts =
|
||||
publicSurfaceArtifacts?.filter(isRuntimeSidecarPublicSurfaceArtifact) ?? undefined;
|
||||
const publicSurfaceArtifacts = collectTopLevelPublicSurfaceArtifacts({
|
||||
pluginDir: source.pluginDir,
|
||||
sourceEntry,
|
||||
setupEntry: setupEntry?.source,
|
||||
});
|
||||
const runtimeSidecarArtifacts = collectRuntimeSidecarArtifacts(publicSurfaceArtifacts);
|
||||
const channelConfigs = await collectBundledChannelConfigsForSource({ source, manifest });
|
||||
if (channelConfigs) {
|
||||
manifest.channelConfigs = channelConfigs;
|
||||
@@ -443,7 +461,7 @@ export async function collectBundledPluginMetadata(params = {}) {
|
||||
? { setupSource: { source: setupEntry.source, built: setupEntry.built } }
|
||||
: {}),
|
||||
...(publicSurfaceArtifacts ? { publicSurfaceArtifacts } : {}),
|
||||
...(runtimeSidecarArtifacts?.length ? { runtimeSidecarArtifacts } : {}),
|
||||
...(runtimeSidecarArtifacts ? { runtimeSidecarArtifacts } : {}),
|
||||
...(typeof packageJson.name === "string" ? { packageName: packageJson.name.trim() } : {}),
|
||||
...(typeof packageJson.version === "string"
|
||||
? { packageVersion: packageJson.version.trim() }
|
||||
|
||||
@@ -3,6 +3,17 @@ import { getBundledChannelRuntimeMap } from "./bundled-channel-config-runtime.js
|
||||
import type { ChannelsConfig } from "./types.channels.js";
|
||||
import { ChannelHeartbeatVisibilitySchema } from "./zod-schema.channels.js";
|
||||
import { GroupPolicySchema } from "./zod-schema.core.js";
|
||||
import {
|
||||
BlueBubblesConfigSchema,
|
||||
DiscordConfigSchema,
|
||||
GoogleChatConfigSchema,
|
||||
IMessageConfigSchema,
|
||||
MSTeamsConfigSchema,
|
||||
SignalConfigSchema,
|
||||
SlackConfigSchema,
|
||||
TelegramConfigSchema,
|
||||
} from "./zod-schema.providers-core.js";
|
||||
import { WhatsAppConfigSchema } from "./zod-schema.providers-whatsapp.js";
|
||||
|
||||
export * from "./zod-schema.providers-core.js";
|
||||
export * from "./zod-schema.providers-whatsapp.js";
|
||||
@@ -12,6 +23,21 @@ const ChannelModelByChannelSchema = z
|
||||
.record(z.string(), z.record(z.string(), z.string()))
|
||||
.optional();
|
||||
|
||||
const directChannelRuntimeSchemas = new Map<
|
||||
string,
|
||||
{ safeParse: (value: unknown) => ReturnType<z.ZodTypeAny["safeParse"]> }
|
||||
>([
|
||||
["bluebubbles", { safeParse: (value) => BlueBubblesConfigSchema.safeParse(value) }],
|
||||
["discord", { safeParse: (value) => DiscordConfigSchema.safeParse(value) }],
|
||||
["googlechat", { safeParse: (value) => GoogleChatConfigSchema.safeParse(value) }],
|
||||
["imessage", { safeParse: (value) => IMessageConfigSchema.safeParse(value) }],
|
||||
["msteams", { safeParse: (value) => MSTeamsConfigSchema.safeParse(value) }],
|
||||
["signal", { safeParse: (value) => SignalConfigSchema.safeParse(value) }],
|
||||
["slack", { safeParse: (value) => SlackConfigSchema.safeParse(value) }],
|
||||
["telegram", { safeParse: (value) => TelegramConfigSchema.safeParse(value) }],
|
||||
["whatsapp", { safeParse: (value) => WhatsAppConfigSchema.safeParse(value) }],
|
||||
]);
|
||||
|
||||
function addLegacyChannelAcpBindingIssues(
|
||||
value: unknown,
|
||||
ctx: z.RefinementCtx,
|
||||
@@ -53,6 +79,25 @@ function normalizeBundledChannelConfigs(
|
||||
}
|
||||
|
||||
let next: ChannelsConfig | undefined;
|
||||
for (const [channelId, runtimeSchema] of directChannelRuntimeSchemas) {
|
||||
if (!Object.prototype.hasOwnProperty.call(value, channelId)) {
|
||||
continue;
|
||||
}
|
||||
const parsed = runtimeSchema.safeParse(value[channelId]);
|
||||
if (!parsed.success) {
|
||||
for (const issue of parsed.error.issues) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: issue.message ?? `Invalid channels.${channelId} config.`,
|
||||
path: [channelId, ...(Array.isArray(issue.path) ? issue.path : [])],
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
next ??= { ...value };
|
||||
next[channelId] = parsed.data as ChannelsConfig[string];
|
||||
}
|
||||
|
||||
for (const [channelId, runtimeSchema] of getBundledChannelRuntimeMap()) {
|
||||
if (!Object.prototype.hasOwnProperty.call(value, channelId)) {
|
||||
continue;
|
||||
|
||||
@@ -312,7 +312,7 @@ describe("exec approval forwarder", () => {
|
||||
buttons: [
|
||||
[
|
||||
{ text: "Allow Once", callback_data: "/approve req-1 allow-once" },
|
||||
{ text: "Allow Always", callback_data: "/approve req-1 allow-always" },
|
||||
{ text: "Allow Always", callback_data: "/approve req-1 always" },
|
||||
],
|
||||
[{ text: "Deny", callback_data: "/approve req-1 deny" }],
|
||||
],
|
||||
|
||||
@@ -7,17 +7,20 @@ import { NON_ENV_SECRETREF_MARKER } from "../agents/model-auth-markers.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import type { ModelDefinitionConfig } from "../config/types.models.js";
|
||||
|
||||
vi.mock("../agents/auth-profiles.js", async () => {
|
||||
const profiles = await vi.importActual<typeof import("../agents/auth-profiles/profiles.js")>(
|
||||
"../agents/auth-profiles/profiles.js",
|
||||
);
|
||||
const order = await vi.importActual<typeof import("../agents/auth-profiles/order.js")>(
|
||||
"../agents/auth-profiles/order.js",
|
||||
);
|
||||
const oauth = await vi.importActual<typeof import("../agents/auth-profiles/oauth.js")>(
|
||||
"../agents/auth-profiles/oauth.js",
|
||||
);
|
||||
|
||||
vi.mock("../agents/auth-profiles.js", () => {
|
||||
const normalizeProvider = (provider?: string | null): string =>
|
||||
String(provider ?? "")
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/^z-ai$/, "zai");
|
||||
const dedupeProfileIds = (profileIds: string[]): string[] => [...new Set(profileIds)];
|
||||
const listProfilesForProvider = (
|
||||
store: { profiles?: Record<string, { provider?: string } | undefined> },
|
||||
provider: string,
|
||||
): string[] =>
|
||||
Object.entries(store.profiles ?? {})
|
||||
.filter(([, profile]) => normalizeProvider(profile?.provider) === normalizeProvider(provider))
|
||||
.map(([profileId]) => profileId);
|
||||
const readStore = (agentDir?: string) => {
|
||||
if (!agentDir) {
|
||||
return { version: 1, profiles: {} };
|
||||
@@ -43,24 +46,123 @@ vi.mock("../agents/auth-profiles.js", async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const resolveAuthProfileOrder = (params: {
|
||||
cfg?: { auth?: { profiles?: Record<string, { provider?: string } | undefined> } };
|
||||
store: {
|
||||
profiles: Record<string, { provider?: string } | undefined>;
|
||||
order?: Record<string, string[]>;
|
||||
};
|
||||
provider: string;
|
||||
}): string[] => {
|
||||
const provider = normalizeProvider(params.provider);
|
||||
const configured = Object.entries(params.cfg?.auth?.profiles ?? {})
|
||||
.filter(([, profile]) => normalizeProvider(profile?.provider) === provider)
|
||||
.map(([profileId]) => profileId);
|
||||
if (configured.length > 0) {
|
||||
return dedupeProfileIds(configured);
|
||||
}
|
||||
const ordered = params.store.order?.[params.provider] ?? params.store.order?.[provider];
|
||||
if (ordered?.length) {
|
||||
return dedupeProfileIds(ordered);
|
||||
}
|
||||
return dedupeProfileIds(listProfilesForProvider(params.store, provider));
|
||||
};
|
||||
|
||||
const resolveApiKeyForProfile = async (params: {
|
||||
store: {
|
||||
profiles: Record<
|
||||
string,
|
||||
| {
|
||||
type?: string;
|
||||
provider?: string;
|
||||
key?: string;
|
||||
token?: string;
|
||||
accessToken?: string;
|
||||
email?: string;
|
||||
expires?: number;
|
||||
}
|
||||
| undefined
|
||||
>;
|
||||
};
|
||||
profileId: string;
|
||||
}): Promise<{ apiKey: string; provider: string; email?: string } | null> => {
|
||||
const cred = params.store.profiles[params.profileId];
|
||||
if (!cred) {
|
||||
return null;
|
||||
}
|
||||
const profileProvider = normalizeProvider(params.profileId.split(":")[0] ?? "");
|
||||
const credentialProvider = normalizeProvider(cred.provider);
|
||||
if (profileProvider && credentialProvider && profileProvider !== credentialProvider) {
|
||||
return null;
|
||||
}
|
||||
if (cred.type === "api_key") {
|
||||
return cred.key ? { apiKey: cred.key, provider: cred.provider ?? profileProvider } : null;
|
||||
}
|
||||
if (cred.type === "token") {
|
||||
if (typeof cred.expires === "number" && cred.expires <= Date.now()) {
|
||||
return null;
|
||||
}
|
||||
return cred.token
|
||||
? { apiKey: cred.token, provider: cred.provider ?? profileProvider, email: cred.email }
|
||||
: null;
|
||||
}
|
||||
if (cred.type === "oauth") {
|
||||
if (typeof cred.expires === "number" && cred.expires <= Date.now()) {
|
||||
return null;
|
||||
}
|
||||
const token = cred.accessToken ?? cred.token;
|
||||
return token
|
||||
? { apiKey: token, provider: cred.provider ?? profileProvider, email: cred.email }
|
||||
: null;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
return {
|
||||
clearRuntimeAuthProfileStoreSnapshots: () => {},
|
||||
ensureAuthProfileStore: (agentDir?: string) => readStore(agentDir),
|
||||
dedupeProfileIds: profiles.dedupeProfileIds,
|
||||
listProfilesForProvider: profiles.listProfilesForProvider,
|
||||
resolveApiKeyForProfile: oauth.resolveApiKeyForProfile,
|
||||
resolveAuthProfileOrder: order.resolveAuthProfileOrder,
|
||||
dedupeProfileIds,
|
||||
listProfilesForProvider,
|
||||
resolveApiKeyForProfile,
|
||||
resolveAuthProfileOrder,
|
||||
};
|
||||
});
|
||||
|
||||
const resolveProviderUsageAuthWithPluginMock = vi.fn(async (..._args: unknown[]) => null);
|
||||
|
||||
vi.mock("../plugins/provider-runtime.js", () => ({
|
||||
resolveProviderUsageAuthWithPlugin: resolveProviderUsageAuthWithPluginMock,
|
||||
const providerRuntimeMocks = vi.hoisted(() => ({
|
||||
resolveProviderUsageAuthWithPluginMock: vi.fn(async (..._args: unknown[]) => null),
|
||||
providerRuntimeMock: {
|
||||
augmentModelCatalogWithProviderPlugins: vi.fn((catalog: unknown) => catalog),
|
||||
buildProviderAuthDoctorHintWithPlugin: vi.fn(() => undefined),
|
||||
buildProviderMissingAuthMessageWithPlugin: vi.fn(() => undefined),
|
||||
buildProviderUnknownModelHintWithPlugin: vi.fn(() => undefined),
|
||||
clearProviderRuntimeHookCache: vi.fn(() => {}),
|
||||
createProviderEmbeddingProvider: vi.fn(() => undefined),
|
||||
formatProviderAuthProfileApiKeyWithPlugin: vi.fn(() => undefined),
|
||||
normalizeProviderResolvedModelWithPlugin: vi.fn(() => undefined),
|
||||
prepareProviderDynamicModel: vi.fn(async () => {}),
|
||||
prepareProviderExtraParams: vi.fn(() => undefined),
|
||||
prepareProviderRuntimeAuth: vi.fn(async () => undefined),
|
||||
refreshProviderOAuthCredentialWithPlugin: vi.fn(async () => undefined),
|
||||
resetProviderRuntimeHookCacheForTest: vi.fn(() => {}),
|
||||
resolveProviderBinaryThinking: vi.fn(() => undefined),
|
||||
resolveProviderBuiltInModelSuppression: vi.fn(() => undefined),
|
||||
resolveProviderCacheTtlEligibility: vi.fn(() => undefined),
|
||||
resolveProviderCapabilitiesWithPlugin: vi.fn(() => undefined),
|
||||
resolveProviderDefaultThinkingLevel: vi.fn(() => undefined),
|
||||
resolveProviderModernModelRef: vi.fn(() => undefined),
|
||||
resolveProviderRuntimePlugin: vi.fn(() => undefined),
|
||||
resolveProviderStreamFn: vi.fn(() => undefined),
|
||||
resolveProviderSyntheticAuthWithPlugin: vi.fn(() => undefined),
|
||||
resolveProviderUsageSnapshotWithPlugin: vi.fn(async () => undefined),
|
||||
resolveProviderXHighThinking: vi.fn(() => undefined),
|
||||
runProviderDynamicModel: vi.fn(() => undefined),
|
||||
wrapProviderStreamFn: vi.fn(() => undefined),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("../plugins/provider-runtime.ts", () => ({
|
||||
resolveProviderUsageAuthWithPlugin: resolveProviderUsageAuthWithPluginMock,
|
||||
vi.mock("../plugins/provider-runtime.js", () => ({
|
||||
...providerRuntimeMocks.providerRuntimeMock,
|
||||
resolveProviderUsageAuthWithPlugin: providerRuntimeMocks.resolveProviderUsageAuthWithPluginMock,
|
||||
}));
|
||||
|
||||
vi.mock("../agents/cli-credentials.js", () => ({
|
||||
@@ -104,8 +206,8 @@ describe("resolveProviderAuths key normalization", () => {
|
||||
({ clearConfigCache } = await import("../config/config.js"));
|
||||
clearConfigCache();
|
||||
clearRuntimeAuthProfileStoreSnapshots();
|
||||
resolveProviderUsageAuthWithPluginMock.mockReset();
|
||||
resolveProviderUsageAuthWithPluginMock.mockResolvedValue(null);
|
||||
providerRuntimeMocks.resolveProviderUsageAuthWithPluginMock.mockReset();
|
||||
providerRuntimeMocks.resolveProviderUsageAuthWithPluginMock.mockResolvedValue(null);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -4,13 +4,13 @@ const resolveProviderUsageAuthWithPluginMock = vi.fn(
|
||||
async (..._args: unknown[]): Promise<unknown> => null,
|
||||
);
|
||||
|
||||
const resolveProviderCapabilitiesWithPluginMock = vi.fn(() => undefined);
|
||||
|
||||
vi.mock("../plugins/provider-runtime.js", async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import("../plugins/provider-runtime.js")>()),
|
||||
resolveProviderCapabilitiesWithPlugin: resolveProviderCapabilitiesWithPluginMock,
|
||||
resolveProviderUsageAuthWithPlugin: resolveProviderUsageAuthWithPluginMock,
|
||||
}));
|
||||
vi.mock("../plugins/provider-runtime.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../plugins/provider-runtime.js")>();
|
||||
return {
|
||||
...actual,
|
||||
resolveProviderUsageAuthWithPlugin: resolveProviderUsageAuthWithPluginMock,
|
||||
};
|
||||
});
|
||||
|
||||
let resolveProviderAuths: typeof import("./provider-usage.auth.js").resolveProviderAuths;
|
||||
|
||||
|
||||
@@ -110,9 +110,9 @@ async function resolveOAuthToken(params: {
|
||||
}
|
||||
try {
|
||||
const resolved = await resolveApiKeyForProfile({
|
||||
// Usage snapshots should work even if config profile metadata is stale.
|
||||
// (e.g. config says api_key but the store has a token profile.)
|
||||
cfg: undefined,
|
||||
// Reuse the already-resolved config snapshot for token/ref resolution so
|
||||
// usage snapshots don't trigger a second ambient loadConfig() call.
|
||||
cfg: params.state.cfg,
|
||||
store: params.state.store,
|
||||
profileId,
|
||||
agentDir: params.state.agentDir,
|
||||
|
||||
@@ -16,9 +16,13 @@ vi.mock("./ports-lsof.js", () => ({
|
||||
resolveLsofCommandSync: (...args: unknown[]) => resolveLsofCommandSyncMock(...args),
|
||||
}));
|
||||
|
||||
vi.mock("../config/paths.js", () => ({
|
||||
resolveGatewayPort: (...args: unknown[]) => resolveGatewayPortMock(...args),
|
||||
}));
|
||||
vi.mock("../config/paths.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../config/paths.js")>();
|
||||
return {
|
||||
...actual,
|
||||
resolveGatewayPort: (...args: unknown[]) => resolveGatewayPortMock(...args),
|
||||
};
|
||||
});
|
||||
|
||||
let __testing: typeof import("./restart-stale-pids.js").__testing;
|
||||
let cleanStaleGatewayProcessesSync: typeof import("./restart-stale-pids.js").cleanStaleGatewayProcessesSync;
|
||||
@@ -38,9 +42,13 @@ beforeEach(async () => {
|
||||
vi.doMock("./ports-lsof.js", () => ({
|
||||
resolveLsofCommandSync: (...args: unknown[]) => resolveLsofCommandSyncMock(...args),
|
||||
}));
|
||||
vi.doMock("../config/paths.js", () => ({
|
||||
resolveGatewayPort: (...args: unknown[]) => resolveGatewayPortMock(...args),
|
||||
}));
|
||||
vi.doMock("../config/paths.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../config/paths.js")>();
|
||||
return {
|
||||
...actual,
|
||||
resolveGatewayPort: (...args: unknown[]) => resolveGatewayPortMock(...args),
|
||||
};
|
||||
});
|
||||
({ __testing, cleanStaleGatewayProcessesSync, findGatewayPidsOnPortSync } =
|
||||
await import("./restart-stale-pids.js"));
|
||||
spawnSyncMock.mockReset();
|
||||
|
||||
@@ -1,43 +1,58 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import ts from "typescript";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const libraryPath = resolve(dirname(fileURLToPath(import.meta.url)), "library.ts");
|
||||
const lazyRuntimeSpecifiers = [
|
||||
"./auto-reply/reply.runtime.js",
|
||||
"./cli/prompt.js",
|
||||
"./infra/binaries.js",
|
||||
"./process/exec.js",
|
||||
"./plugins/runtime/runtime-whatsapp-boundary.js",
|
||||
] as const;
|
||||
|
||||
function readLibraryModuleImports() {
|
||||
const sourceText = readFileSync(libraryPath, "utf8");
|
||||
const sourceFile = ts.createSourceFile(libraryPath, sourceText, ts.ScriptTarget.Latest, true);
|
||||
const staticImports = new Set<string>();
|
||||
const dynamicImports = new Set<string>();
|
||||
|
||||
function visit(node: ts.Node) {
|
||||
if (
|
||||
ts.isImportDeclaration(node) &&
|
||||
ts.isStringLiteral(node.moduleSpecifier) &&
|
||||
!node.importClause?.isTypeOnly
|
||||
) {
|
||||
staticImports.add(node.moduleSpecifier.text);
|
||||
}
|
||||
|
||||
if (
|
||||
ts.isCallExpression(node) &&
|
||||
node.expression.kind === ts.SyntaxKind.ImportKeyword &&
|
||||
node.arguments.length === 1 &&
|
||||
ts.isStringLiteral(node.arguments[0])
|
||||
) {
|
||||
dynamicImports.add(node.arguments[0].text);
|
||||
}
|
||||
|
||||
ts.forEachChild(node, visit);
|
||||
}
|
||||
|
||||
visit(sourceFile);
|
||||
return { dynamicImports, staticImports };
|
||||
}
|
||||
|
||||
describe("library module imports", () => {
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
});
|
||||
it("keeps lazy runtime boundaries on dynamic imports", () => {
|
||||
const { dynamicImports, staticImports } = readLibraryModuleImports();
|
||||
|
||||
it("does not load lazy runtimes on module import", async () => {
|
||||
const replyRuntimeLoads = vi.fn();
|
||||
const promptRuntimeLoads = vi.fn();
|
||||
const binariesRuntimeLoads = vi.fn();
|
||||
const whatsappRuntimeLoads = vi.fn();
|
||||
vi.doMock("./auto-reply/reply.runtime.js", async (importOriginal) => {
|
||||
replyRuntimeLoads();
|
||||
return await importOriginal<typeof import("./auto-reply/reply.runtime.js")>();
|
||||
});
|
||||
vi.doMock("./cli/prompt.runtime.js", async (importOriginal) => {
|
||||
promptRuntimeLoads();
|
||||
return await importOriginal<typeof import("./cli/prompt.runtime.js")>();
|
||||
});
|
||||
vi.doMock("./infra/binaries.runtime.js", async (importOriginal) => {
|
||||
binariesRuntimeLoads();
|
||||
return await importOriginal<typeof import("./infra/binaries.runtime.js")>();
|
||||
});
|
||||
vi.doMock("./plugins/runtime/runtime-whatsapp-boundary.js", async (importOriginal) => {
|
||||
whatsappRuntimeLoads();
|
||||
return await importOriginal<
|
||||
typeof import("./plugins/runtime/runtime-whatsapp-boundary.js")
|
||||
>();
|
||||
});
|
||||
|
||||
await import("./library.js");
|
||||
|
||||
expect(replyRuntimeLoads).not.toHaveBeenCalled();
|
||||
// Vitest eagerly resolves some manual mocks for runtime-boundary modules
|
||||
// even when the lazy wrapper is not invoked. Keep the assertion on the
|
||||
// reply runtime, which is the stable import-time contract this test cares about.
|
||||
vi.doUnmock("./auto-reply/reply.runtime.js");
|
||||
vi.doUnmock("./cli/prompt.runtime.js");
|
||||
vi.doUnmock("./infra/binaries.runtime.js");
|
||||
vi.doUnmock("./plugins/runtime/runtime-whatsapp-boundary.js");
|
||||
for (const specifier of lazyRuntimeSpecifiers) {
|
||||
expect(staticImports.has(specifier), `${specifier} should stay lazy`).toBe(false);
|
||||
expect(dynamicImports.has(specifier), `${specifier} should remain dynamically imported`).toBe(
|
||||
true,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,61 +1,76 @@
|
||||
import type { getReplyFromConfig as getReplyFromConfigRuntime } from "./auto-reply/reply.runtime.js";
|
||||
import { applyTemplate } from "./auto-reply/templating.js";
|
||||
import { createDefaultDeps } from "./cli/deps.js";
|
||||
import type { promptYesNo as promptYesNoRuntime } from "./cli/prompt.js";
|
||||
import { waitForever } from "./cli/wait.js";
|
||||
import { loadConfig } from "./config/config.js";
|
||||
import { resolveStorePath } from "./config/sessions/paths.js";
|
||||
import { deriveSessionKey, resolveSessionKey } from "./config/sessions/session-key.js";
|
||||
import { loadSessionStore, saveSessionStore } from "./config/sessions/store.js";
|
||||
import type { ensureBinary as ensureBinaryRuntime } from "./infra/binaries.js";
|
||||
import {
|
||||
describePortOwner,
|
||||
ensurePortAvailable,
|
||||
handlePortError,
|
||||
PortInUseError,
|
||||
} from "./infra/ports.js";
|
||||
import type { monitorWebChannel as monitorWebChannelRuntime } from "./plugins/runtime/runtime-whatsapp-boundary.js";
|
||||
import type {
|
||||
runCommandWithTimeout as runCommandWithTimeoutRuntime,
|
||||
runExec as runExecRuntime,
|
||||
} from "./process/exec.js";
|
||||
import { assertWebChannel, normalizeE164, toWhatsappJid } from "./utils.js";
|
||||
|
||||
type ReplyRuntimeModule = typeof import("./auto-reply/reply.runtime.js");
|
||||
type PromptRuntimeModule = typeof import("./cli/prompt.runtime.js");
|
||||
type BinariesRuntimeModule = typeof import("./infra/binaries.runtime.js");
|
||||
type ExecRuntimeModule = typeof import("./process/exec.js");
|
||||
type WhatsAppRuntimeModule = typeof import("./plugins/runtime/runtime-whatsapp-boundary.js");
|
||||
type GetReplyFromConfig = typeof getReplyFromConfigRuntime;
|
||||
type PromptYesNo = typeof promptYesNoRuntime;
|
||||
type EnsureBinary = typeof ensureBinaryRuntime;
|
||||
type RunExec = typeof runExecRuntime;
|
||||
type RunCommandWithTimeout = typeof runCommandWithTimeoutRuntime;
|
||||
type MonitorWebChannel = typeof monitorWebChannelRuntime;
|
||||
|
||||
let replyRuntimePromise: Promise<ReplyRuntimeModule> | undefined;
|
||||
let promptRuntimePromise: Promise<PromptRuntimeModule> | undefined;
|
||||
let binariesRuntimePromise: Promise<BinariesRuntimeModule> | undefined;
|
||||
let execRuntimePromise: Promise<ExecRuntimeModule> | undefined;
|
||||
let whatsappRuntimePromise: Promise<WhatsAppRuntimeModule> | undefined;
|
||||
let replyRuntimePromise: Promise<typeof import("./auto-reply/reply.runtime.js")> | null = null;
|
||||
let promptRuntimePromise: Promise<typeof import("./cli/prompt.js")> | null = null;
|
||||
let binariesRuntimePromise: Promise<typeof import("./infra/binaries.js")> | null = null;
|
||||
let execRuntimePromise: Promise<typeof import("./process/exec.js")> | null = null;
|
||||
let whatsappRuntimePromise: Promise<
|
||||
typeof import("./plugins/runtime/runtime-whatsapp-boundary.js")
|
||||
> | null = null;
|
||||
|
||||
function loadReplyRuntime(): Promise<ReplyRuntimeModule> {
|
||||
return (replyRuntimePromise ??= import("./auto-reply/reply.runtime.js"));
|
||||
function loadReplyRuntime() {
|
||||
replyRuntimePromise ??= import("./auto-reply/reply.runtime.js");
|
||||
return replyRuntimePromise;
|
||||
}
|
||||
|
||||
function loadPromptRuntime(): Promise<PromptRuntimeModule> {
|
||||
return (promptRuntimePromise ??= import("./cli/prompt.runtime.js"));
|
||||
function loadPromptRuntime() {
|
||||
promptRuntimePromise ??= import("./cli/prompt.js");
|
||||
return promptRuntimePromise;
|
||||
}
|
||||
|
||||
function loadBinariesRuntime(): Promise<BinariesRuntimeModule> {
|
||||
return (binariesRuntimePromise ??= import("./infra/binaries.runtime.js"));
|
||||
function loadBinariesRuntime() {
|
||||
binariesRuntimePromise ??= import("./infra/binaries.js");
|
||||
return binariesRuntimePromise;
|
||||
}
|
||||
|
||||
function loadExecRuntime(): Promise<ExecRuntimeModule> {
|
||||
return (execRuntimePromise ??= import("./process/exec.js"));
|
||||
function loadExecRuntime() {
|
||||
execRuntimePromise ??= import("./process/exec.js");
|
||||
return execRuntimePromise;
|
||||
}
|
||||
|
||||
function loadWhatsAppRuntime(): Promise<WhatsAppRuntimeModule> {
|
||||
return (whatsappRuntimePromise ??= import("./plugins/runtime/runtime-whatsapp-boundary.js"));
|
||||
function loadWhatsAppRuntime() {
|
||||
whatsappRuntimePromise ??= import("./plugins/runtime/runtime-whatsapp-boundary.js");
|
||||
return whatsappRuntimePromise;
|
||||
}
|
||||
|
||||
export const getReplyFromConfig: ReplyRuntimeModule["getReplyFromConfig"] = async (...args) =>
|
||||
export const getReplyFromConfig: GetReplyFromConfig = async (...args) =>
|
||||
(await loadReplyRuntime()).getReplyFromConfig(...args);
|
||||
export const promptYesNo: PromptRuntimeModule["promptYesNo"] = async (...args) =>
|
||||
export const promptYesNo: PromptYesNo = async (...args) =>
|
||||
(await loadPromptRuntime()).promptYesNo(...args);
|
||||
export const ensureBinary: BinariesRuntimeModule["ensureBinary"] = async (...args) =>
|
||||
export const ensureBinary: EnsureBinary = async (...args) =>
|
||||
(await loadBinariesRuntime()).ensureBinary(...args);
|
||||
export const runExec: ExecRuntimeModule["runExec"] = async (...args) =>
|
||||
(await loadExecRuntime()).runExec(...args);
|
||||
export const runCommandWithTimeout: ExecRuntimeModule["runCommandWithTimeout"] = async (...args) =>
|
||||
export const runExec: RunExec = async (...args) => (await loadExecRuntime()).runExec(...args);
|
||||
export const runCommandWithTimeout: RunCommandWithTimeout = async (...args) =>
|
||||
(await loadExecRuntime()).runCommandWithTimeout(...args);
|
||||
export const monitorWebChannel: WhatsAppRuntimeModule["monitorWebChannel"] = async (...args) =>
|
||||
export const monitorWebChannel: MonitorWebChannel = async (...args) =>
|
||||
(await loadWhatsAppRuntime()).monitorWebChannel(...args);
|
||||
|
||||
export {
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { getCommandPathWithRootOptions } from "../cli/argv.js";
|
||||
import { loadConfig, type OpenClawConfig } from "../config/config.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { resolveNodeRequireFromMeta } from "./node-require.js";
|
||||
|
||||
type LoggingConfig = OpenClawConfig["logging"];
|
||||
|
||||
const requireConfig = resolveNodeRequireFromMeta(import.meta.url);
|
||||
|
||||
export function shouldSkipMutatingLoggingConfigRead(argv: string[] = process.argv): boolean {
|
||||
const [primary, secondary] = getCommandPathWithRootOptions(argv, 2);
|
||||
return primary === "config" && (secondary === "schema" || secondary === "validate");
|
||||
@@ -13,7 +16,12 @@ export function readLoggingConfig(): LoggingConfig | undefined {
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
const parsed = loadConfig();
|
||||
const loaded = requireConfig?.("../config/config.js") as
|
||||
| {
|
||||
loadConfig?: () => OpenClawConfig;
|
||||
}
|
||||
| undefined;
|
||||
const parsed = loaded?.loadConfig?.();
|
||||
const logging = parsed?.logging;
|
||||
if (!logging || typeof logging !== "object" || Array.isArray(logging)) {
|
||||
return undefined;
|
||||
|
||||
@@ -12,13 +12,11 @@ const bundledCoverageEntrySources = buildPluginSdkEntrySources(bundledRepresenta
|
||||
|
||||
describe("plugin-sdk bundled exports", () => {
|
||||
it("emits importable bundled subpath entries", { timeout: 120_000 }, async () => {
|
||||
const bundleTempRoot = path.join(
|
||||
process.cwd(),
|
||||
"node_modules",
|
||||
".cache",
|
||||
"openclaw-plugin-sdk-build",
|
||||
const bundleCacheRoot = path.join(process.cwd(), "node_modules", ".cache");
|
||||
await fs.mkdir(bundleCacheRoot, { recursive: true });
|
||||
const bundleTempRoot = await fs.mkdtemp(
|
||||
path.join(bundleCacheRoot, "openclaw-plugin-sdk-build-"),
|
||||
);
|
||||
await fs.mkdir(bundleTempRoot, { recursive: true });
|
||||
const outDir = path.join(bundleTempRoot, "bundle");
|
||||
await fs.rm(outDir, { recursive: true, force: true });
|
||||
await fs.mkdir(outDir, { recursive: true });
|
||||
|
||||
@@ -1027,8 +1027,8 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [
|
||||
properties: {},
|
||||
},
|
||||
enabledByDefault: true,
|
||||
autoEnableWhenConfiguredProviders: ["copilot-proxy"],
|
||||
providers: ["copilot-proxy"],
|
||||
autoEnableWhenConfiguredProviders: ["copilot-proxy"],
|
||||
providerAuthChoices: [
|
||||
{
|
||||
provider: "copilot-proxy",
|
||||
@@ -5487,8 +5487,8 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [
|
||||
},
|
||||
},
|
||||
enabledByDefault: true,
|
||||
autoEnableWhenConfiguredProviders: ["google-gemini-cli"],
|
||||
providers: ["google", "google-gemini-cli"],
|
||||
autoEnableWhenConfiguredProviders: ["google-gemini-cli"],
|
||||
cliBackends: ["google-gemini-cli"],
|
||||
providerAuthEnvVars: {
|
||||
google: ["GEMINI_API_KEY", "GOOGLE_API_KEY"],
|
||||
@@ -9586,9 +9586,9 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [
|
||||
properties: {},
|
||||
},
|
||||
enabledByDefault: true,
|
||||
legacyPluginIds: ["minimax-portal-auth"],
|
||||
autoEnableWhenConfiguredProviders: ["minimax-portal"],
|
||||
providers: ["minimax", "minimax-portal"],
|
||||
autoEnableWhenConfiguredProviders: ["minimax-portal"],
|
||||
legacyPluginIds: ["minimax-portal-auth"],
|
||||
providerAuthEnvVars: {
|
||||
minimax: ["MINIMAX_API_KEY"],
|
||||
"minimax-portal": ["MINIMAX_OAUTH_TOKEN", "MINIMAX_API_KEY"],
|
||||
|
||||
@@ -310,6 +310,17 @@ export function resolveGatewayStartupPluginIds(params: {
|
||||
if (plugin.channels.length > 0) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
plugin.origin === "bundled" &&
|
||||
(plugin.providers.some((providerId) =>
|
||||
configuredActivationIds.has(normalizeProviderId(providerId)),
|
||||
) ||
|
||||
plugin.cliBackends.some((backendId) =>
|
||||
configuredActivationIds.has(normalizeProviderId(backendId)),
|
||||
))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
const enabled = resolveEffectiveEnableState({
|
||||
id: plugin.id,
|
||||
origin: plugin.origin,
|
||||
@@ -323,16 +334,6 @@ export function resolveGatewayStartupPluginIds(params: {
|
||||
if (plugin.origin !== "bundled") {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
plugin.providers.some((providerId) =>
|
||||
configuredActivationIds.has(normalizeProviderId(providerId)),
|
||||
) ||
|
||||
plugin.cliBackends.some((backendId) =>
|
||||
configuredActivationIds.has(normalizeProviderId(backendId)),
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return (
|
||||
pluginsConfig.allow.includes(plugin.id) ||
|
||||
pluginsConfig.entries[plugin.id]?.enabled === true ||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { parseExplicitTargetForChannel } from "../channels/plugins/target-parsing.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { logVerbose } from "../globals.js";
|
||||
import { parseTelegramTarget } from "../plugin-sdk/telegram-runtime.js";
|
||||
import {
|
||||
clearPluginCommands,
|
||||
clearPluginCommandsForPlugin,
|
||||
@@ -118,6 +119,30 @@ function stripPrefix(raw: string | undefined, prefix: string): string | undefine
|
||||
return raw.startsWith(prefix) ? raw.slice(prefix.length) : raw;
|
||||
}
|
||||
|
||||
function parseDiscordBindingTarget(raw: string | undefined): {
|
||||
conversationId: string;
|
||||
} | null {
|
||||
if (!raw) {
|
||||
return null;
|
||||
}
|
||||
if (raw.startsWith("slash:")) {
|
||||
return null;
|
||||
}
|
||||
const normalized = raw.startsWith("discord:") ? raw.slice("discord:".length) : raw;
|
||||
if (!normalized) {
|
||||
return null;
|
||||
}
|
||||
if (normalized.startsWith("channel:")) {
|
||||
const id = normalized.slice("channel:".length).trim();
|
||||
return id ? { conversationId: `channel:${id}` } : null;
|
||||
}
|
||||
if (normalized.startsWith("user:")) {
|
||||
const id = normalized.slice("user:".length).trim();
|
||||
return id ? { conversationId: `user:${id}` } : null;
|
||||
}
|
||||
return /^\d+$/.test(normalized.trim()) ? { conversationId: `user:${normalized.trim()}` } : null;
|
||||
}
|
||||
|
||||
function resolveBindingConversationFromCommand(params: {
|
||||
channel: string;
|
||||
from?: string;
|
||||
@@ -138,34 +163,35 @@ function resolveBindingConversationFromCommand(params: {
|
||||
return null;
|
||||
}
|
||||
const target = parseExplicitTargetForChannel("telegram", rawTarget);
|
||||
if (!target) {
|
||||
const fallbackTarget = target ? null : parseTelegramTarget(rawTarget);
|
||||
if (!target && !fallbackTarget) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
channel: "telegram",
|
||||
accountId,
|
||||
conversationId: target.to,
|
||||
threadId: params.messageThreadId ?? target.threadId,
|
||||
conversationId: target?.to ?? fallbackTarget?.chatId ?? "",
|
||||
threadId: params.messageThreadId ?? target?.threadId ?? fallbackTarget?.messageThreadId,
|
||||
};
|
||||
}
|
||||
if (params.channel === "discord") {
|
||||
const source = params.from ?? params.to;
|
||||
const rawTarget = source?.startsWith("discord:channel:")
|
||||
? stripPrefix(source, "discord:")
|
||||
: source?.startsWith("discord:user:")
|
||||
? stripPrefix(source, "discord:")
|
||||
: source;
|
||||
if (!rawTarget || rawTarget.startsWith("slash:")) {
|
||||
const rawTarget = source?.startsWith("discord:") ? stripPrefix(source, "discord:") : source;
|
||||
if (!rawTarget) {
|
||||
return null;
|
||||
}
|
||||
const target = parseExplicitTargetForChannel("discord", rawTarget);
|
||||
const target =
|
||||
parseExplicitTargetForChannel("discord", rawTarget) ?? parseDiscordBindingTarget(rawTarget);
|
||||
if (!target) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
channel: "discord",
|
||||
accountId,
|
||||
conversationId: `${target.chatType === "direct" ? "user" : "channel"}:${target.to}`,
|
||||
conversationId:
|
||||
"conversationId" in target
|
||||
? target.conversationId
|
||||
: `${target.chatType === "direct" ? "user" : "channel"}:${target.to}`,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -45,6 +45,7 @@ let openaiProvider: ProviderPlugin;
|
||||
|
||||
describe("provider catalog contract", { timeout: PROVIDER_CATALOG_CONTRACT_TIMEOUT_MS }, () => {
|
||||
beforeAll(async () => {
|
||||
vi.resetModules();
|
||||
const openaiPlugin = await import("../../../extensions/openai/index.ts");
|
||||
openaiProviders = registerProviderPlugin({
|
||||
plugin: openaiPlugin.default,
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
import { HUGGINGFACE_DEFAULT_MODEL_REF } from "../../extensions/huggingface/api.js";
|
||||
import { LITELLM_DEFAULT_MODEL_REF } from "../../extensions/litellm/api.js";
|
||||
import { OPENROUTER_DEFAULT_MODEL_REF } from "../../extensions/openrouter/api.js";
|
||||
import { TOGETHER_DEFAULT_MODEL_REF } from "../../extensions/together/api.js";
|
||||
import { VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF } from "../../extensions/vercel-ai-gateway/api.js";
|
||||
import { XIAOMI_DEFAULT_MODEL_REF } from "../../extensions/xiaomi/api.js";
|
||||
import { ZAI_DEFAULT_MODEL_REF } from "../../extensions/zai/api.js";
|
||||
import { resolveOpenClawAgentDir } from "../agents/agent-paths.js";
|
||||
import { upsertAuthProfile } from "../agents/auth-profiles.js";
|
||||
import type { SecretInput } from "../config/types.secrets.js";
|
||||
@@ -17,6 +10,13 @@ import {
|
||||
import { KILOCODE_DEFAULT_MODEL_REF } from "./provider-model-kilocode.js";
|
||||
|
||||
const resolveAuthAgentDir = (agentDir?: string) => agentDir ?? resolveOpenClawAgentDir();
|
||||
const ZAI_DEFAULT_MODEL_REF = "zai/glm-5";
|
||||
const XIAOMI_DEFAULT_MODEL_REF = "xiaomi/mimo-v2-flash";
|
||||
const OPENROUTER_DEFAULT_MODEL_REF = "openrouter/auto";
|
||||
const HUGGINGFACE_DEFAULT_MODEL_REF = "huggingface/deepseek-ai/DeepSeek-R1";
|
||||
const TOGETHER_DEFAULT_MODEL_REF = "together/moonshotai/Kimi-K2.5";
|
||||
const LITELLM_DEFAULT_MODEL_REF = "litellm/claude-opus-4-6";
|
||||
const VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF = "vercel-ai-gateway/anthropic/claude-opus-4.6";
|
||||
|
||||
type ProviderApiKeySetter = (
|
||||
key: SecretInput,
|
||||
|
||||
@@ -5,7 +5,7 @@ export const openaiCodexCatalogEntries = [
|
||||
{ provider: "openai", id: "gpt-5.2-pro", name: "GPT-5.2 Pro" },
|
||||
{ provider: "openai", id: "gpt-5-mini", name: "GPT-5 mini" },
|
||||
{ provider: "openai", id: "gpt-5-nano", name: "GPT-5 nano" },
|
||||
{ provider: "openai-codex", id: "gpt-5.4", name: "GPT-5.4" },
|
||||
{ provider: "openai-codex", id: "gpt-5.3-codex", name: "GPT-5.3 Codex" },
|
||||
];
|
||||
|
||||
export const expectedAugmentedOpenaiCodexCatalogEntries = [
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import {
|
||||
ZAI_CN_BASE_URL,
|
||||
ZAI_CODING_CN_BASE_URL,
|
||||
ZAI_CODING_GLOBAL_BASE_URL,
|
||||
ZAI_GLOBAL_BASE_URL,
|
||||
} from "../../extensions/zai/api.js";
|
||||
import { fetchWithTimeout } from "../utils/fetch-timeout.js";
|
||||
|
||||
export type ZaiEndpointId = "global" | "cn" | "coding-global" | "coding-cn";
|
||||
const ZAI_CODING_GLOBAL_BASE_URL = "https://api.z.ai/api/coding/paas/v4";
|
||||
const ZAI_CODING_CN_BASE_URL = "https://open.bigmodel.cn/api/coding/paas/v4";
|
||||
const ZAI_GLOBAL_BASE_URL = "https://api.z.ai/api/paas/v4";
|
||||
const ZAI_CN_BASE_URL = "https://open.bigmodel.cn/api/paas/v4";
|
||||
|
||||
export type ZaiDetectedEndpoint = {
|
||||
endpoint: ZaiEndpointId;
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
buildPublishedInstallScenarios,
|
||||
collectInstalledPackageErrors,
|
||||
} from "../scripts/openclaw-npm-postpublish-verify.ts";
|
||||
import { BUNDLED_RUNTIME_SIDECAR_PATHS } from "../src/plugins/public-artifacts.ts";
|
||||
|
||||
describe("buildPublishedInstallScenarios", () => {
|
||||
it("uses a single fresh scenario for plain stable releases", () => {
|
||||
@@ -41,12 +42,10 @@ describe("collectInstalledPackageErrors", () => {
|
||||
}),
|
||||
).toEqual([
|
||||
"installed package version mismatch: expected 2026.3.23-2, found 2026.3.23.",
|
||||
"installed package is missing required bundled runtime sidecar: dist/extensions/whatsapp/light-runtime-api.js",
|
||||
"installed package is missing required bundled runtime sidecar: dist/extensions/whatsapp/runtime-api.js",
|
||||
"installed package is missing required bundled runtime sidecar: dist/extensions/matrix/helper-api.js",
|
||||
"installed package is missing required bundled runtime sidecar: dist/extensions/matrix/runtime-api.js",
|
||||
"installed package is missing required bundled runtime sidecar: dist/extensions/matrix/thread-bindings-runtime.js",
|
||||
"installed package is missing required bundled runtime sidecar: dist/extensions/msteams/runtime-api.js",
|
||||
...BUNDLED_RUNTIME_SIDECAR_PATHS.map(
|
||||
(relativePath) =>
|
||||
`installed package is missing required bundled runtime sidecar: ${relativePath}`,
|
||||
),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -371,9 +371,13 @@ describe("scripts/test-parallel lane planning", () => {
|
||||
}),
|
||||
);
|
||||
|
||||
expect(output).toContain("unit-batch-1 filters=50");
|
||||
expect(output).toContain("unit-batch-2 filters=49");
|
||||
expect(output).not.toContain("unit-batch-3");
|
||||
const unitBatchLines = getPlanLines(output, "unit-batch-");
|
||||
const unitBatchFilterCounts = unitBatchLines.map((line) =>
|
||||
parseNumericPlanField(line, "filters"),
|
||||
);
|
||||
|
||||
expect(unitBatchLines.length).toBe(2);
|
||||
expect(unitBatchFilterCounts).toEqual([50, 50]);
|
||||
});
|
||||
|
||||
it("explains targeted file ownership and execution policy", () => {
|
||||
|
||||
Reference in New Issue
Block a user