mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-13 15:47:28 +00:00
refactor: break runtime import cycles
This commit is contained in:
@@ -2,7 +2,7 @@ import {
|
||||
createModelCatalogPresetAppliers,
|
||||
type OpenClawConfig,
|
||||
} from "openclaw/plugin-sdk/provider-onboard";
|
||||
import { ARCEE_BASE_URL } from "./api.js";
|
||||
import { ARCEE_BASE_URL } from "./models.js";
|
||||
import {
|
||||
buildArceeCatalogModels,
|
||||
buildArceeOpenRouterCatalogModels,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import { buildArceeModelDefinition, ARCEE_BASE_URL, ARCEE_MODEL_CATALOG } from "./api.js";
|
||||
import { buildArceeModelDefinition, ARCEE_BASE_URL, ARCEE_MODEL_CATALOG } from "./models.js";
|
||||
|
||||
export const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { rawDataToString } from "../infra/ws.js";
|
||||
import { redactSensitiveText } from "../logging/redact.js";
|
||||
import { getDirectAgentForCdp, withNoProxyForCdpUrl } from "./cdp-proxy-bypass.js";
|
||||
import { CDP_HTTP_REQUEST_TIMEOUT_MS, CDP_WS_HANDSHAKE_TIMEOUT_MS } from "./cdp-timeouts.js";
|
||||
import { resolveBrowserRateLimitMessage } from "./client-fetch.js";
|
||||
import { resolveBrowserRateLimitMessage } from "./rate-limit-message.js";
|
||||
|
||||
export { isLoopbackHost };
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
createBrowserControlContext,
|
||||
startBrowserControlServiceFromConfig,
|
||||
} from "./control-service.js";
|
||||
import { resolveBrowserRateLimitMessage } from "./rate-limit-message.js";
|
||||
import { createBrowserRouteDispatcher } from "./routes/dispatcher.js";
|
||||
|
||||
// Application-level error from the browser control service (service is reachable
|
||||
@@ -104,36 +105,10 @@ const BROWSER_TOOL_MODEL_HINT =
|
||||
"Do NOT retry the browser tool — it will keep failing. " +
|
||||
"Use an alternative approach or inform the user that the browser is currently unavailable.";
|
||||
|
||||
const BROWSER_SERVICE_RATE_LIMIT_MESSAGE =
|
||||
"Browser service rate limit reached. " +
|
||||
"Wait for the current session to complete, or retry later.";
|
||||
|
||||
const BROWSERBASE_RATE_LIMIT_MESSAGE =
|
||||
"Browserbase rate limit reached (max concurrent sessions). " +
|
||||
"Wait for the current session to complete, or upgrade your plan.";
|
||||
|
||||
function isRateLimitStatus(status: number): boolean {
|
||||
return status === 429;
|
||||
}
|
||||
|
||||
function isBrowserbaseUrl(url: string): boolean {
|
||||
if (!isAbsoluteHttp(url)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const host = normalizeLowercaseStringOrEmpty(new URL(url).hostname);
|
||||
return host === "browserbase.com" || host.endsWith(".browserbase.com");
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveBrowserRateLimitMessage(url: string): string {
|
||||
return isBrowserbaseUrl(url)
|
||||
? BROWSERBASE_RATE_LIMIT_MESSAGE
|
||||
: BROWSER_SERVICE_RATE_LIMIT_MESSAGE;
|
||||
}
|
||||
|
||||
function resolveBrowserFetchOperatorHint(url: string): string {
|
||||
const isLocal = !isAbsoluteHttp(url);
|
||||
return isLocal
|
||||
|
||||
31
extensions/browser/src/browser/rate-limit-message.ts
Normal file
31
extensions/browser/src/browser/rate-limit-message.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
|
||||
const BROWSER_SERVICE_RATE_LIMIT_MESSAGE =
|
||||
"Browser service rate limit reached. " +
|
||||
"Wait for the current session to complete, or retry later.";
|
||||
|
||||
const BROWSERBASE_RATE_LIMIT_MESSAGE =
|
||||
"Browserbase rate limit reached (max concurrent sessions). " +
|
||||
"Wait for the current session to complete, or upgrade your plan.";
|
||||
|
||||
function isAbsoluteHttp(url: string): boolean {
|
||||
return /^https?:\/\//i.test(url.trim());
|
||||
}
|
||||
|
||||
function isBrowserbaseUrl(url: string): boolean {
|
||||
if (!isAbsoluteHttp(url)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const host = normalizeLowercaseStringOrEmpty(new URL(url).hostname);
|
||||
return host === "browserbase.com" || host.endsWith(".browserbase.com");
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveBrowserRateLimitMessage(url: string): string {
|
||||
return isBrowserbaseUrl(url)
|
||||
? BROWSERBASE_RATE_LIMIT_MESSAGE
|
||||
: BROWSER_SERVICE_RATE_LIMIT_MESSAGE;
|
||||
}
|
||||
@@ -1,17 +1,19 @@
|
||||
import fsPromises from "node:fs/promises";
|
||||
import { loadConfig } from "openclaw/plugin-sdk/browser-config-runtime";
|
||||
import { withTimeout } from "openclaw/plugin-sdk/browser-node-runtime";
|
||||
import { detectMime } from "openclaw/plugin-sdk/browser-setup-tools";
|
||||
import { redactCdpUrl } from "../browser/cdp.helpers.js";
|
||||
import { resolveBrowserConfig } from "../browser/config.js";
|
||||
import {
|
||||
isPersistentBrowserProfileMutation,
|
||||
normalizeBrowserRequestPath,
|
||||
resolveRequestedBrowserProfile,
|
||||
} from "../browser/request-policy.js";
|
||||
import { createBrowserRouteDispatcher } from "../browser/routes/dispatcher.js";
|
||||
import {
|
||||
createBrowserControlContext,
|
||||
createBrowserRouteDispatcher,
|
||||
detectMime,
|
||||
isPersistentBrowserProfileMutation,
|
||||
loadConfig,
|
||||
normalizeBrowserRequestPath,
|
||||
redactCdpUrl,
|
||||
resolveBrowserConfig,
|
||||
resolveRequestedBrowserProfile,
|
||||
startBrowserControlServiceFromConfig,
|
||||
withTimeout,
|
||||
} from "../core-api.js";
|
||||
} from "../control-service.js";
|
||||
|
||||
type BrowserProxyParams = {
|
||||
method?: string;
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
BYTEPLUS_CODING_BASE_URL,
|
||||
BYTEPLUS_CODING_MODEL_CATALOG,
|
||||
BYTEPLUS_MODEL_CATALOG,
|
||||
} from "./api.js";
|
||||
} from "./models.js";
|
||||
|
||||
export function buildBytePlusProvider(): ModelProviderConfig {
|
||||
return {
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
CHUTES_DEFAULT_MODEL_REF,
|
||||
CHUTES_MODEL_CATALOG,
|
||||
buildChutesModelDefinition,
|
||||
} from "./api.js";
|
||||
} from "./models.js";
|
||||
|
||||
export { CHUTES_DEFAULT_MODEL_REF };
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
CHUTES_MODEL_CATALOG,
|
||||
buildChutesModelDefinition,
|
||||
discoverChutesModels,
|
||||
} from "./api.js";
|
||||
} from "./models.js";
|
||||
|
||||
/**
|
||||
* Build the Chutes provider with dynamic model discovery.
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import { buildDeepSeekModelDefinition, DEEPSEEK_BASE_URL, DEEPSEEK_MODEL_CATALOG } from "./api.js";
|
||||
import {
|
||||
buildDeepSeekModelDefinition,
|
||||
DEEPSEEK_BASE_URL,
|
||||
DEEPSEEK_MODEL_CATALOG,
|
||||
} from "./models.js";
|
||||
|
||||
export function buildDeepSeekProvider(): ModelProviderConfig {
|
||||
return {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { EventEmitter } from "node:events";
|
||||
import type { DiscordGatewayHandle } from "./monitor/gateway-handle.js";
|
||||
import {
|
||||
DiscordGatewayEvent,
|
||||
@@ -6,6 +5,8 @@ import {
|
||||
DiscordGatewaySupervisor,
|
||||
} from "./monitor/gateway-supervisor.js";
|
||||
|
||||
export { getDiscordGatewayEmitter } from "./monitor/gateway-supervisor.js";
|
||||
|
||||
export type WaitForDiscordGatewayStopParams = {
|
||||
gateway?: DiscordGatewayHandle;
|
||||
abortSignal?: AbortSignal;
|
||||
@@ -14,10 +15,6 @@ export type WaitForDiscordGatewayStopParams = {
|
||||
registerForceStop?: (forceStop: (err: unknown) => void) => void;
|
||||
};
|
||||
|
||||
export function getDiscordGatewayEmitter(gateway?: unknown): EventEmitter | undefined {
|
||||
return (gateway as { emitter?: EventEmitter } | undefined)?.emitter;
|
||||
}
|
||||
|
||||
export async function waitForDiscordGatewayStop(
|
||||
params: WaitForDiscordGatewayStopParams,
|
||||
): Promise<void> {
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { EventEmitter } from "node:events";
|
||||
import { danger } from "openclaw/plugin-sdk/runtime-env";
|
||||
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import { getDiscordGatewayEmitter } from "../monitor.gateway.js";
|
||||
|
||||
export type DiscordGatewayEventType =
|
||||
| "disallowed-intents"
|
||||
@@ -29,6 +28,10 @@ export class DiscordGatewayLifecycleError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
export function getDiscordGatewayEmitter(gateway?: unknown): EventEmitter | undefined {
|
||||
return (gateway as { emitter?: EventEmitter } | undefined)?.emitter;
|
||||
}
|
||||
|
||||
export type DiscordGatewaySupervisor = {
|
||||
emitter?: EventEmitter;
|
||||
attachLifecycle: (handler: (event: DiscordGatewayEvent) => void) => void;
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { OpenClawPluginApi } from "openclaw/plugin-sdk/channel-entry-contra
|
||||
|
||||
function registerSlashCommandRoute(api: OpenClawPluginApi): void {
|
||||
const register = loadBundledEntryExportSync<(api: OpenClawPluginApi) => void>(import.meta.url, {
|
||||
specifier: "./runtime-api.js",
|
||||
specifier: "./slash-route-api.js",
|
||||
exportName: "registerSlashCommandRoute",
|
||||
});
|
||||
register(api);
|
||||
|
||||
@@ -86,5 +86,4 @@ export {
|
||||
resolveChannelMediaMaxBytes,
|
||||
} from "openclaw/plugin-sdk/media-runtime";
|
||||
export { normalizeProviderId } from "openclaw/plugin-sdk/provider-model-shared";
|
||||
export { registerSlashCommandRoute } from "./src/mattermost/slash-state.js";
|
||||
export { setMattermostRuntime } from "./src/runtime.js";
|
||||
|
||||
1
extensions/mattermost/slash-route-api.ts
Normal file
1
extensions/mattermost/slash-route-api.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { registerSlashCommandRoute } from "./src/mattermost/slash-state.js";
|
||||
@@ -16,13 +16,13 @@ import {
|
||||
import { z } from "openclaw/plugin-sdk/zod";
|
||||
import {
|
||||
createFixedWindowRateLimiter,
|
||||
isBlockedHostnameOrIp,
|
||||
readJsonBodyWithLimit,
|
||||
requestBodyErrorToText,
|
||||
} from "../api.js";
|
||||
} from "../runtime-api.js";
|
||||
import { publishNostrProfile, getNostrProfileState } from "./channel.js";
|
||||
import { NostrProfileSchema, type NostrProfile } from "./config-schema.js";
|
||||
import { importProfileFromRelays, mergeProfiles } from "./nostr-profile-import.js";
|
||||
import { validateUrlSafety } from "./nostr-profile-url-safety.js";
|
||||
|
||||
// ============================================================================
|
||||
// Types
|
||||
@@ -103,30 +103,6 @@ async function withPublishLock<T>(accountId: string, fn: () => Promise<T>): Prom
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SSRF Protection
|
||||
// ============================================================================
|
||||
|
||||
function validateUrlSafety(urlStr: string): { ok: true } | { ok: false; error: string } {
|
||||
try {
|
||||
const url = new URL(urlStr);
|
||||
|
||||
if (url.protocol !== "https:") {
|
||||
return { ok: false, error: "URL must use https:// protocol" };
|
||||
}
|
||||
|
||||
const hostname = normalizeLowercaseStringOrEmpty(url.hostname);
|
||||
|
||||
if (isBlockedHostnameOrIp(hostname)) {
|
||||
return { ok: false, error: "URL must not point to private/internal addresses" };
|
||||
}
|
||||
|
||||
return { ok: true };
|
||||
} catch {
|
||||
return { ok: false, error: "Invalid URL format" };
|
||||
}
|
||||
}
|
||||
|
||||
// Export for use in import validation
|
||||
export { validateUrlSafety };
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import { SimplePool, verifyEvent, type Event } from "nostr-tools";
|
||||
import type { NostrProfile } from "./config-schema.js";
|
||||
import { validateUrlSafety } from "./nostr-profile-http.js";
|
||||
import { validateUrlSafety } from "./nostr-profile-url-safety.js";
|
||||
import { contentToProfile, type ProfileContent } from "./nostr-profile.js";
|
||||
|
||||
// ============================================================================
|
||||
|
||||
22
extensions/nostr/src/nostr-profile-url-safety.ts
Normal file
22
extensions/nostr/src/nostr-profile-url-safety.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { isBlockedHostnameOrIp } from "../runtime-api.js";
|
||||
|
||||
export function validateUrlSafety(urlStr: string): { ok: true } | { ok: false; error: string } {
|
||||
try {
|
||||
const url = new URL(urlStr);
|
||||
|
||||
if (url.protocol !== "https:") {
|
||||
return { ok: false, error: "URL must use https:// protocol" };
|
||||
}
|
||||
|
||||
const hostname = normalizeLowercaseStringOrEmpty(url.hostname);
|
||||
|
||||
if (isBlockedHostnameOrIp(hostname)) {
|
||||
return { ok: false, error: "URL must not point to private/internal addresses" };
|
||||
}
|
||||
|
||||
return { ok: true };
|
||||
} catch {
|
||||
return { ok: false, error: "Invalid URL format" };
|
||||
}
|
||||
}
|
||||
@@ -15,5 +15,6 @@ export * from "./src/scenario.js";
|
||||
export * from "./src/scenario-catalog.js";
|
||||
export * from "./src/self-check-scenario.js";
|
||||
export * from "./src/self-check.js";
|
||||
export * from "./src/self-check-runner.js";
|
||||
export * from "./src/gateway-child.js";
|
||||
export * from "./src/suite.js";
|
||||
|
||||
15
extensions/qa-lab/src/self-check-runner.ts
Normal file
15
extensions/qa-lab/src/self-check-runner.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { startQaLabServer } from "./lab-server.js";
|
||||
|
||||
export async function runQaLabSelfCheck(params?: { repoRoot?: string; outputPath?: string }) {
|
||||
const server = await startQaLabServer({
|
||||
repoRoot: params?.repoRoot,
|
||||
outputPath: params?.outputPath,
|
||||
});
|
||||
try {
|
||||
return await server.runSelfCheck();
|
||||
} finally {
|
||||
await server.stop();
|
||||
}
|
||||
}
|
||||
|
||||
export const runQaE2eSelfCheck = runQaLabSelfCheck;
|
||||
@@ -2,7 +2,6 @@ import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { QaBusState } from "./bus-state.js";
|
||||
import { startQaLabServer } from "./lab-server.js";
|
||||
import { renderQaMarkdownReport } from "./report.js";
|
||||
import { runQaScenario, type QaScenarioResult } from "./scenario.js";
|
||||
import { createQaSelfCheckScenario } from "./self-check-scenario.js";
|
||||
@@ -87,17 +86,3 @@ export async function runQaSelfCheckAgainstState(params: {
|
||||
scenarioResult,
|
||||
};
|
||||
}
|
||||
|
||||
export async function runQaLabSelfCheck(params?: { repoRoot?: string; outputPath?: string }) {
|
||||
const server = await startQaLabServer({
|
||||
repoRoot: params?.repoRoot,
|
||||
outputPath: params?.outputPath,
|
||||
});
|
||||
try {
|
||||
return await server.runSelfCheck();
|
||||
} finally {
|
||||
await server.stop();
|
||||
}
|
||||
}
|
||||
|
||||
export const runQaE2eSelfCheck = runQaLabSelfCheck;
|
||||
|
||||
@@ -2,7 +2,11 @@ import {
|
||||
createModelCatalogPresetAppliers,
|
||||
type OpenClawConfig,
|
||||
} from "openclaw/plugin-sdk/provider-onboard";
|
||||
import { buildTogetherModelDefinition, TOGETHER_BASE_URL, TOGETHER_MODEL_CATALOG } from "./api.js";
|
||||
import {
|
||||
buildTogetherModelDefinition,
|
||||
TOGETHER_BASE_URL,
|
||||
TOGETHER_MODEL_CATALOG,
|
||||
} from "./models.js";
|
||||
|
||||
export const TOGETHER_DEFAULT_MODEL_REF = "together/moonshotai/Kimi-K2.5";
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import { buildTogetherModelDefinition, TOGETHER_BASE_URL, TOGETHER_MODEL_CATALOG } from "./api.js";
|
||||
import {
|
||||
buildTogetherModelDefinition,
|
||||
TOGETHER_BASE_URL,
|
||||
TOGETHER_MODEL_CATALOG,
|
||||
} from "./models.js";
|
||||
|
||||
export function buildTogetherProvider(): ModelProviderConfig {
|
||||
return {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
import { describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
|
||||
import { buildChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-schema";
|
||||
import { createChatChannelPlugin } from "openclaw/plugin-sdk/channel-core";
|
||||
import {
|
||||
createLoggedPairingApprovalNotifier,
|
||||
@@ -17,7 +18,6 @@ import {
|
||||
createDefaultChannelRuntimeState,
|
||||
} from "openclaw/plugin-sdk/status-helpers";
|
||||
import type { OpenClawConfig } from "../api.js";
|
||||
import { buildChannelConfigSchema } from "../api.js";
|
||||
import { twitchMessageActions } from "./actions.js";
|
||||
import { removeClientManager } from "./client-manager-registry.js";
|
||||
import { TwitchConfigSchema } from "./config-schema.js";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import { discoverVeniceModels, VENICE_BASE_URL } from "./api.js";
|
||||
import { discoverVeniceModels, VENICE_BASE_URL } from "./models.js";
|
||||
|
||||
export async function buildVeniceProvider(): Promise<ModelProviderConfig> {
|
||||
const models = await discoverVeniceModels();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import { discoverVercelAiGatewayModels, VERCEL_AI_GATEWAY_BASE_URL } from "./api.js";
|
||||
import { discoverVercelAiGatewayModels, VERCEL_AI_GATEWAY_BASE_URL } from "./models.js";
|
||||
|
||||
export async function buildVercelAiGatewayProvider(): Promise<ModelProviderConfig> {
|
||||
return {
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
DOUBAO_CODING_BASE_URL,
|
||||
DOUBAO_CODING_MODEL_CATALOG,
|
||||
DOUBAO_MODEL_CATALOG,
|
||||
} from "./api.js";
|
||||
} from "./models.js";
|
||||
|
||||
export function buildDoubaoProvider(): ModelProviderConfig {
|
||||
return {
|
||||
|
||||
@@ -3,8 +3,8 @@ import type {
|
||||
ProviderRuntimeModel,
|
||||
} from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { normalizeModelCompat } from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import { applyXaiModelCompat } from "openclaw/plugin-sdk/provider-tools";
|
||||
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { applyXaiModelCompat } from "./api.js";
|
||||
import { resolveXaiCatalogEntry, XAI_BASE_URL } from "./model-definitions.js";
|
||||
|
||||
const XAI_MODERN_MODEL_PREFIXES = ["grok-3", "grok-4", "grok-code-fast"] as const;
|
||||
|
||||
@@ -7,7 +7,7 @@ import type {
|
||||
} from "../../context-engine/types.js";
|
||||
import { formatErrorMessage } from "../../infra/errors.js";
|
||||
import { emitSessionTranscriptUpdate } from "../../sessions/transcript-events.js";
|
||||
import { getRawSessionAppendMessage } from "../session-tool-result-guard.js";
|
||||
import { getRawSessionAppendMessage } from "../session-raw-append-message.js";
|
||||
import { acquireSessionWriteLock } from "../session-write-lock.js";
|
||||
import { log } from "./logger.js";
|
||||
|
||||
|
||||
24
src/agents/session-raw-append-message.ts
Normal file
24
src/agents/session-raw-append-message.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { SessionManager } from "@mariozechner/pi-coding-agent";
|
||||
|
||||
const RAW_APPEND_MESSAGE = Symbol("openclaw.session.rawAppendMessage");
|
||||
|
||||
export type SessionManagerWithRawAppend = SessionManager & {
|
||||
[RAW_APPEND_MESSAGE]?: SessionManager["appendMessage"];
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the unguarded appendMessage implementation for a session manager.
|
||||
*/
|
||||
export function getRawSessionAppendMessage(
|
||||
sessionManager: SessionManager,
|
||||
): SessionManager["appendMessage"] {
|
||||
const rawAppend = (sessionManager as SessionManagerWithRawAppend)[RAW_APPEND_MESSAGE];
|
||||
return rawAppend ?? sessionManager.appendMessage.bind(sessionManager);
|
||||
}
|
||||
|
||||
export function setRawSessionAppendMessage(
|
||||
sessionManager: SessionManager,
|
||||
appendMessage: SessionManager["appendMessage"],
|
||||
): void {
|
||||
(sessionManager as SessionManagerWithRawAppend)[RAW_APPEND_MESSAGE] = appendMessage;
|
||||
}
|
||||
@@ -11,14 +11,13 @@ import {
|
||||
DEFAULT_MAX_LIVE_TOOL_RESULT_CHARS,
|
||||
truncateToolResultMessage,
|
||||
} from "./pi-embedded-runner/tool-result-truncation.js";
|
||||
import {
|
||||
getRawSessionAppendMessage,
|
||||
setRawSessionAppendMessage,
|
||||
} from "./session-raw-append-message.js";
|
||||
import { createPendingToolCallState } from "./session-tool-result-state.js";
|
||||
import { makeMissingToolResult, sanitizeToolCallInputs } from "./session-transcript-repair.js";
|
||||
import { extractToolCallsFromAssistant, extractToolResultId } from "./tool-call-id.js";
|
||||
const RAW_APPEND_MESSAGE = Symbol("openclaw.session.rawAppendMessage");
|
||||
|
||||
type SessionManagerWithRawAppend = SessionManager & {
|
||||
[RAW_APPEND_MESSAGE]?: SessionManager["appendMessage"];
|
||||
};
|
||||
|
||||
/**
|
||||
* Truncate oversized text content blocks in a tool result message.
|
||||
@@ -63,15 +62,7 @@ function normalizePersistedToolResultName(
|
||||
return toolResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the unguarded appendMessage implementation for a session manager.
|
||||
*/
|
||||
export function getRawSessionAppendMessage(
|
||||
sessionManager: SessionManager,
|
||||
): SessionManager["appendMessage"] {
|
||||
const rawAppend = (sessionManager as SessionManagerWithRawAppend)[RAW_APPEND_MESSAGE];
|
||||
return rawAppend ?? sessionManager.appendMessage.bind(sessionManager);
|
||||
}
|
||||
export { getRawSessionAppendMessage };
|
||||
|
||||
export function installSessionToolResultGuard(
|
||||
sessionManager: SessionManager,
|
||||
@@ -115,7 +106,7 @@ export function installSessionToolResultGuard(
|
||||
getPendingIds: () => string[];
|
||||
} {
|
||||
const originalAppend = getRawSessionAppendMessage(sessionManager);
|
||||
(sessionManager as SessionManagerWithRawAppend)[RAW_APPEND_MESSAGE] = originalAppend;
|
||||
setRawSessionAppendMessage(sessionManager, originalAppend);
|
||||
const pendingState = createPendingToolCallState();
|
||||
const persistMessage = (message: AgentMessage) => {
|
||||
const transformer = opts?.transformMessageForPersistence;
|
||||
|
||||
@@ -11,4 +11,4 @@ export { resolveExternalBestEffortDeliveryTarget } from "../infra/outbound/best-
|
||||
export { createBoundDeliveryRouter } from "../infra/outbound/bound-delivery-router.js";
|
||||
export { resolveConversationIdFromTargets } from "../infra/outbound/conversation-id.js";
|
||||
export { getGlobalHookRunner } from "../plugins/hook-runner-global.js";
|
||||
export { isEmbeddedPiRunActive, queueEmbeddedPiMessage } from "./pi-embedded.js";
|
||||
export { isEmbeddedPiRunActive, queueEmbeddedPiMessage } from "./pi-embedded-runner/runs.js";
|
||||
|
||||
@@ -10,4 +10,4 @@ export {
|
||||
isEmbeddedPiRunActive,
|
||||
queueEmbeddedPiMessage,
|
||||
waitForEmbeddedPiRunEnd,
|
||||
} from "./pi-embedded.js";
|
||||
} from "./pi-embedded-runner/runs.js";
|
||||
|
||||
@@ -15,7 +15,7 @@ import { formatErrorMessage } from "../infra/errors.js";
|
||||
import { isSubagentSessionKey, parseAgentSessionKey } from "../routing/session-key.js";
|
||||
import { INTERNAL_MESSAGE_CHANNEL } from "../utils/message-channel.js";
|
||||
import { AGENT_LANE_SUBAGENT } from "./lanes.js";
|
||||
import { abortEmbeddedPiRun } from "./pi-embedded.js";
|
||||
import { abortEmbeddedPiRun } from "./pi-embedded-runner/runs.js";
|
||||
import {
|
||||
readLatestAssistantReplySnapshot,
|
||||
waitForAgentRunAndReadUpdatedAssistantReply,
|
||||
|
||||
@@ -2,13 +2,15 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { DEFAULT_AGENT_MAX_CONCURRENT, DEFAULT_SUBAGENT_MAX_CONCURRENT } from "./agent-limits.js";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
applyProviderConfigDefaultsWithPlugin: vi.fn(),
|
||||
applyProviderConfigDefaultsForConfig: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../plugins/provider-runtime.js", () => ({
|
||||
applyProviderConfigDefaultsWithPlugin: (
|
||||
...args: Parameters<typeof mocks.applyProviderConfigDefaultsWithPlugin>
|
||||
) => mocks.applyProviderConfigDefaultsWithPlugin(...args),
|
||||
vi.mock("./provider-policy.js", () => ({
|
||||
applyProviderConfigDefaultsForConfig: (
|
||||
...args: Parameters<typeof mocks.applyProviderConfigDefaultsForConfig>
|
||||
) => mocks.applyProviderConfigDefaultsForConfig(...args),
|
||||
normalizeProviderConfigForConfigDefaults: (_params: { providerConfig: unknown }) =>
|
||||
_params.providerConfig,
|
||||
}));
|
||||
|
||||
let applyContextPruningDefaults: typeof import("./defaults.js").applyContextPruningDefaults;
|
||||
@@ -20,7 +22,7 @@ describe("config defaults", () => {
|
||||
vi.resetModules();
|
||||
({ applyAgentDefaults, applyContextPruningDefaults, applyMessageDefaults } =
|
||||
await import("./defaults.js"));
|
||||
mocks.applyProviderConfigDefaultsWithPlugin.mockReset();
|
||||
mocks.applyProviderConfigDefaultsForConfig.mockReset();
|
||||
});
|
||||
|
||||
it("skips provider defaults when agent defaults are absent", () => {
|
||||
@@ -35,7 +37,7 @@ describe("config defaults", () => {
|
||||
};
|
||||
|
||||
expect(applyContextPruningDefaults(cfg as never)).toBe(cfg);
|
||||
expect(mocks.applyProviderConfigDefaultsWithPlugin).not.toHaveBeenCalled();
|
||||
expect(mocks.applyProviderConfigDefaultsForConfig).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("uses anthropic provider defaults when agent defaults exist", () => {
|
||||
@@ -53,10 +55,10 @@ describe("config defaults", () => {
|
||||
},
|
||||
},
|
||||
};
|
||||
mocks.applyProviderConfigDefaultsWithPlugin.mockReturnValue(nextCfg);
|
||||
mocks.applyProviderConfigDefaultsForConfig.mockReturnValue(nextCfg);
|
||||
|
||||
expect(applyContextPruningDefaults(cfg as never)).toBe(nextCfg);
|
||||
expect(mocks.applyProviderConfigDefaultsWithPlugin).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.applyProviderConfigDefaultsForConfig).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("defaults ackReactionScope without deriving other message fields", () => {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { DEFAULT_CONTEXT_TOKENS } from "../agents/defaults.js";
|
||||
import { normalizeProviderSpecificConfig } from "../agents/models-config.providers.policy.js";
|
||||
import { normalizeProviderId } from "../agents/provider-id.js";
|
||||
import { applyProviderConfigDefaultsWithPlugin } from "../plugins/provider-runtime.js";
|
||||
import { DEFAULT_AGENT_MAX_CONCURRENT, DEFAULT_SUBAGENT_MAX_CONCURRENT } from "./agent-limits.js";
|
||||
import {
|
||||
applyProviderConfigDefaultsForConfig,
|
||||
normalizeProviderConfigForConfigDefaults,
|
||||
} from "./provider-policy.js";
|
||||
import { normalizeTalkConfig } from "./talk.js";
|
||||
import type { OpenClawConfig } from "./types.js";
|
||||
import type { ModelDefinitionConfig } from "./types.models.js";
|
||||
@@ -139,7 +141,10 @@ export function applyModelDefaults(cfg: OpenClawConfig): OpenClawConfig {
|
||||
if (providerConfig) {
|
||||
const nextProviders = { ...providerConfig };
|
||||
for (const [providerId, provider] of Object.entries(providerConfig)) {
|
||||
const normalizedProvider = normalizeProviderSpecificConfig(providerId, provider);
|
||||
const normalizedProvider = normalizeProviderConfigForConfigDefaults({
|
||||
provider: providerId,
|
||||
providerConfig: provider,
|
||||
});
|
||||
const models = normalizedProvider.models;
|
||||
if (!Array.isArray(models) || models.length === 0) {
|
||||
if (normalizedProvider !== provider) {
|
||||
@@ -339,13 +344,10 @@ export function applyContextPruningDefaults(cfg: OpenClawConfig): OpenClawConfig
|
||||
return cfg;
|
||||
}
|
||||
return (
|
||||
applyProviderConfigDefaultsWithPlugin({
|
||||
applyProviderConfigDefaultsForConfig({
|
||||
provider: "anthropic",
|
||||
context: {
|
||||
provider: "anthropic",
|
||||
config: cfg,
|
||||
env: process.env,
|
||||
},
|
||||
config: cfg,
|
||||
env: process.env,
|
||||
}) ?? cfg
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,8 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { DEFAULT_CONTEXT_TOKENS } from "../agents/defaults.js";
|
||||
import { applyModelDefaults } from "./defaults.js";
|
||||
import type { OpenClawConfig } from "./types.js";
|
||||
|
||||
const { normalizeProviderSpecificConfigMock } = vi.hoisted(() => ({
|
||||
normalizeProviderSpecificConfigMock: vi.fn((providerKey: string, provider: unknown) => {
|
||||
if (providerKey !== "anthropic" || !provider || typeof provider !== "object") {
|
||||
return provider;
|
||||
}
|
||||
return { ...(provider as Record<string, unknown>), api: "anthropic-messages" };
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("../agents/models-config.providers.policy.js", () => ({
|
||||
normalizeProviderSpecificConfig: normalizeProviderSpecificConfigMock,
|
||||
}));
|
||||
|
||||
describe("applyModelDefaults", () => {
|
||||
function buildProxyProviderConfig(overrides?: { contextWindow?: number; maxTokens?: number }) {
|
||||
return {
|
||||
|
||||
27
src/config/provider-policy.ts
Normal file
27
src/config/provider-policy.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { resolveBundledProviderPolicySurface } from "../plugins/provider-public-artifacts.js";
|
||||
import type { ModelProviderConfig, OpenClawConfig } from "./types.js";
|
||||
|
||||
export function normalizeProviderConfigForConfigDefaults(params: {
|
||||
provider: string;
|
||||
providerConfig: ModelProviderConfig;
|
||||
}): ModelProviderConfig {
|
||||
const normalized = resolveBundledProviderPolicySurface(params.provider)?.normalizeConfig?.({
|
||||
provider: params.provider,
|
||||
providerConfig: params.providerConfig,
|
||||
});
|
||||
return normalized && normalized !== params.providerConfig ? normalized : params.providerConfig;
|
||||
}
|
||||
|
||||
export function applyProviderConfigDefaultsForConfig(params: {
|
||||
provider: string;
|
||||
config: OpenClawConfig;
|
||||
env: NodeJS.ProcessEnv;
|
||||
}): OpenClawConfig {
|
||||
return (
|
||||
resolveBundledProviderPolicySurface(params.provider)?.applyConfigDefaults?.({
|
||||
provider: params.provider,
|
||||
config: params.config,
|
||||
env: params.env,
|
||||
}) ?? params.config
|
||||
);
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
type TaskAuditSeverity,
|
||||
type TaskAuditSummary,
|
||||
} from "./task-registry.audit.shared.js";
|
||||
import { reconcileInspectableTasks } from "./task-registry.reconcile.js";
|
||||
import type { TaskRecord } from "./task-registry.types.js";
|
||||
|
||||
export type TaskAuditOptions = {
|
||||
@@ -20,6 +19,12 @@ const DEFAULT_STALE_RUNNING_MS = 30 * 60_000;
|
||||
export { createEmptyTaskAuditSummary };
|
||||
export type { TaskAuditCode, TaskAuditFinding, TaskAuditSeverity, TaskAuditSummary };
|
||||
|
||||
let taskAuditTaskProvider: () => TaskRecord[] = () => [];
|
||||
|
||||
export function configureTaskAuditTaskProvider(provider: () => TaskRecord[]): void {
|
||||
taskAuditTaskProvider = provider;
|
||||
}
|
||||
|
||||
function createFinding(params: {
|
||||
severity: TaskAuditSeverity;
|
||||
code: TaskAuditCode;
|
||||
@@ -83,7 +88,7 @@ function compareFindings(left: TaskAuditFinding, right: TaskAuditFinding): numbe
|
||||
}
|
||||
|
||||
export function listTaskAuditFindings(options: TaskAuditOptions = {}): TaskAuditFinding[] {
|
||||
const tasks = options.tasks ?? reconcileInspectableTasks();
|
||||
const tasks = options.tasks ?? taskAuditTaskProvider();
|
||||
const now = options.now ?? Date.now();
|
||||
const staleQueuedMs = options.staleQueuedMs ?? DEFAULT_STALE_QUEUED_MS;
|
||||
const staleRunningMs = options.staleRunningMs ?? DEFAULT_STALE_RUNNING_MS;
|
||||
|
||||
@@ -15,7 +15,11 @@ import {
|
||||
resolveTaskForLookupToken,
|
||||
setTaskCleanupAfterById,
|
||||
} from "./runtime-internal.js";
|
||||
import { listTaskAuditFindings, summarizeTaskAuditFindings } from "./task-registry.audit.js";
|
||||
import {
|
||||
configureTaskAuditTaskProvider,
|
||||
listTaskAuditFindings,
|
||||
summarizeTaskAuditFindings,
|
||||
} from "./task-registry.audit.js";
|
||||
import type { TaskAuditSummary } from "./task-registry.audit.js";
|
||||
import { summarizeTaskRecords } from "./task-registry.summary.js";
|
||||
import type { TaskRecord, TaskRegistrySummary } from "./task-registry.types.js";
|
||||
@@ -229,6 +233,8 @@ export function reconcileInspectableTasks(): TaskRecord[] {
|
||||
.map((task) => reconcileTaskRecordForOperatorInspection(task));
|
||||
}
|
||||
|
||||
configureTaskAuditTaskProvider(reconcileInspectableTasks);
|
||||
|
||||
export function getInspectableTaskRegistrySummary(): TaskRegistrySummary {
|
||||
return summarizeTaskRecords(reconcileInspectableTasks());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user