diff --git a/config/knip.config.ts b/config/knip.config.ts index b3749efe913a..c50623435245 100644 --- a/config/knip.config.ts +++ b/config/knip.config.ts @@ -144,6 +144,7 @@ const config = { entry: rootEntries, ignoreDependencies: [ "@openclaw/*", + "file-type", "playwright-core", "sqlite-vec", "tree-sitter-bash", @@ -184,6 +185,14 @@ const config = { entry: ["src/*.ts!"], project: ["src/**/*.ts!"], }, + "packages/media-core": { + entry: ["src/*.ts!"], + project: ["src/**/*.ts!"], + }, + "packages/acp-core": { + entry: ["src/*.ts!"], + project: ["src/**/*.ts!"], + }, "packages/terminal-core": { entry: ["src/*.ts!"], project: ["src/**/*.ts!"], diff --git a/docs/.generated/plugin-sdk-api-baseline.sha256 b/docs/.generated/plugin-sdk-api-baseline.sha256 index 33311de1facb..e7cf6dc22b67 100644 --- a/docs/.generated/plugin-sdk-api-baseline.sha256 +++ b/docs/.generated/plugin-sdk-api-baseline.sha256 @@ -1,2 +1,2 @@ -71e7b7d92cba7f971a8f8f9ee14045320120df0f137f7c5cf295b333d46ecf8c plugin-sdk-api-baseline.json -c550d232c205924eb9e0df9995c205a68659febf0450d02d79bd3349d25a323c plugin-sdk-api-baseline.jsonl +8b0cb667fc676d9cf9e47ec8b3889aaa1cd75d493cc78428844931ca0ac415ff plugin-sdk-api-baseline.json +b97fddcfae489bb4496e5cba26de388e4fe7eb52584d6a6b8e9cb4e539b258c5 plugin-sdk-api-baseline.jsonl diff --git a/extensions/sms/src/accounts.test.ts b/extensions/sms/src/accounts.test.ts index bdb8f3169b69..4841f04b205f 100644 --- a/extensions/sms/src/accounts.test.ts +++ b/extensions/sms/src/accounts.test.ts @@ -81,6 +81,24 @@ describe("SMS account config", () => { }); }); + it("normalizes numeric allowFrom entries accepted by config schema", () => { + const cfg = { + channels: { + sms: { + accountSid: "AC-parent", + authToken: "parent-token", + fromNumber: "+15550000000", + allowFrom: [1_555_333_4444], + }, + }, + }; + + expect(SmsConfigSchema.parse(cfg.channels.sms).allowFrom).toEqual([1_555_333_4444]); + expect(resolveSmsAccount(cfg)).toMatchObject({ + allowFrom: ["+15553334444"], + }); + }); + it("uses the configured default account when accountId is omitted", () => { const cfg = { channels: { diff --git a/extensions/sms/src/twilio.ts b/extensions/sms/src/twilio.ts index fcad632d378f..d36c0aea68db 100644 --- a/extensions/sms/src/twilio.ts +++ b/extensions/sms/src/twilio.ts @@ -72,11 +72,11 @@ function parseTwilioSuccessPayload(text: string): TwilioMessagePayload { from: typeof record.from === "string" ? record.from : undefined, status: typeof record.status === "string" ? record.status : undefined, }; - } catch (error) { - if (error instanceof Error && error.message === "Twilio SMS send returned malformed JSON.") { - throw error; + } catch (cause) { + if (cause instanceof Error && cause.message === "Twilio SMS send returned malformed JSON.") { + throw cause; } - throw new Error("Twilio SMS send returned malformed JSON.", { cause: error }); + throw new Error("Twilio SMS send returned malformed JSON.", { cause }); } } diff --git a/extensions/sms/src/types.ts b/extensions/sms/src/types.ts index 23f0a121b871..4e667bc7be5e 100644 --- a/extensions/sms/src/types.ts +++ b/extensions/sms/src/types.ts @@ -11,7 +11,7 @@ export type SmsChannelConfigFields = { publicWebhookUrl?: string; dangerouslyDisableSignatureValidation?: boolean; dmPolicy?: "pairing" | "open" | "allowlist" | "disabled"; - allowFrom?: string | string[]; + allowFrom?: string | Array; textChunkLimit?: number; }; diff --git a/extensions/tsconfig.package-boundary.paths.json b/extensions/tsconfig.package-boundary.paths.json index ec6855772d34..9b45504b68bf 100644 --- a/extensions/tsconfig.package-boundary.paths.json +++ b/extensions/tsconfig.package-boundary.paths.json @@ -203,6 +203,66 @@ "@openclaw/media-generation-core/*": [ "../dist/plugin-sdk/packages/media-generation-core/src/*.d.ts" ], + "@openclaw/media-core": [ + "../dist/plugin-sdk/packages/media-core/src/index.d.ts" + ], + "@openclaw/media-core/base64": [ + "../dist/plugin-sdk/packages/media-core/src/base64.d.ts" + ], + "@openclaw/media-core/constants": [ + "../dist/plugin-sdk/packages/media-core/src/constants.d.ts" + ], + "@openclaw/media-core/content-length": [ + "../dist/plugin-sdk/packages/media-core/src/content-length.d.ts" + ], + "@openclaw/media-core/file-name": [ + "../dist/plugin-sdk/packages/media-core/src/file-name.d.ts" + ], + "@openclaw/media-core/inbound-path-policy": [ + "../dist/plugin-sdk/packages/media-core/src/inbound-path-policy.d.ts" + ], + "@openclaw/media-core/inline-image-data-url": [ + "../dist/plugin-sdk/packages/media-core/src/inline-image-data-url.d.ts" + ], + "@openclaw/media-core/media-source-url": [ + "../dist/plugin-sdk/packages/media-core/src/media-source-url.d.ts" + ], + "@openclaw/media-core/mime": [ + "../dist/plugin-sdk/packages/media-core/src/mime.d.ts" + ], + "@openclaw/media-core/read-byte-stream-with-limit": [ + "../dist/plugin-sdk/packages/media-core/src/read-byte-stream-with-limit.d.ts" + ], + "@openclaw/media-core/read-response-with-limit": [ + "../dist/plugin-sdk/packages/media-core/src/read-response-with-limit.d.ts" + ], + "@openclaw/media-core/*": [ + "../dist/plugin-sdk/packages/media-core/src/*.d.ts" + ], + "@openclaw/normalization-core/record-coerce": [ + "../dist/plugin-sdk/packages/normalization-core/src/record-coerce.d.ts" + ], + "@openclaw/normalization-core/string-coerce": [ + "../dist/plugin-sdk/packages/normalization-core/src/string-coerce.d.ts" + ], + "@openclaw/normalization-core/*": [ + "../dist/plugin-sdk/packages/normalization-core/src/*.d.ts" + ], + "@openclaw/acp-core": [ + "../dist/plugin-sdk/packages/acp-core/src/index.d.ts" + ], + "@openclaw/acp-core/normalize-text": [ + "../dist/plugin-sdk/packages/acp-core/src/normalize-text.d.ts" + ], + "@openclaw/acp-core/record-shared": [ + "../dist/plugin-sdk/packages/acp-core/src/record-shared.d.ts" + ], + "@openclaw/acp-core/runtime/types": [ + "../dist/plugin-sdk/packages/acp-core/src/runtime/types.d.ts" + ], + "@openclaw/acp-core/*": [ + "../dist/plugin-sdk/packages/acp-core/src/*.d.ts" + ], "@openclaw/terminal-core": [ "../dist/plugin-sdk/packages/terminal-core/src/index.d.ts" ], diff --git a/extensions/xai/tsconfig.json b/extensions/xai/tsconfig.json index c56d2289abd4..eb2c1c4ce4d7 100644 --- a/extensions/xai/tsconfig.json +++ b/extensions/xai/tsconfig.json @@ -189,6 +189,66 @@ "@openclaw/media-generation-core/*": [ "../../dist/plugin-sdk/packages/media-generation-core/src/*.d.ts" ], + "@openclaw/media-core": [ + "../../dist/plugin-sdk/packages/media-core/src/index.d.ts" + ], + "@openclaw/media-core/base64": [ + "../../dist/plugin-sdk/packages/media-core/src/base64.d.ts" + ], + "@openclaw/media-core/constants": [ + "../../dist/plugin-sdk/packages/media-core/src/constants.d.ts" + ], + "@openclaw/media-core/content-length": [ + "../../dist/plugin-sdk/packages/media-core/src/content-length.d.ts" + ], + "@openclaw/media-core/file-name": [ + "../../dist/plugin-sdk/packages/media-core/src/file-name.d.ts" + ], + "@openclaw/media-core/inbound-path-policy": [ + "../../dist/plugin-sdk/packages/media-core/src/inbound-path-policy.d.ts" + ], + "@openclaw/media-core/inline-image-data-url": [ + "../../dist/plugin-sdk/packages/media-core/src/inline-image-data-url.d.ts" + ], + "@openclaw/media-core/media-source-url": [ + "../../dist/plugin-sdk/packages/media-core/src/media-source-url.d.ts" + ], + "@openclaw/media-core/mime": [ + "../../dist/plugin-sdk/packages/media-core/src/mime.d.ts" + ], + "@openclaw/media-core/read-byte-stream-with-limit": [ + "../../dist/plugin-sdk/packages/media-core/src/read-byte-stream-with-limit.d.ts" + ], + "@openclaw/media-core/read-response-with-limit": [ + "../../dist/plugin-sdk/packages/media-core/src/read-response-with-limit.d.ts" + ], + "@openclaw/media-core/*": [ + "../../dist/plugin-sdk/packages/media-core/src/*.d.ts" + ], + "@openclaw/normalization-core/record-coerce": [ + "../../dist/plugin-sdk/packages/normalization-core/src/record-coerce.d.ts" + ], + "@openclaw/normalization-core/string-coerce": [ + "../../dist/plugin-sdk/packages/normalization-core/src/string-coerce.d.ts" + ], + "@openclaw/normalization-core/*": [ + "../../dist/plugin-sdk/packages/normalization-core/src/*.d.ts" + ], + "@openclaw/acp-core": [ + "../../dist/plugin-sdk/packages/acp-core/src/index.d.ts" + ], + "@openclaw/acp-core/normalize-text": [ + "../../dist/plugin-sdk/packages/acp-core/src/normalize-text.d.ts" + ], + "@openclaw/acp-core/record-shared": [ + "../../dist/plugin-sdk/packages/acp-core/src/record-shared.d.ts" + ], + "@openclaw/acp-core/runtime/types": [ + "../../dist/plugin-sdk/packages/acp-core/src/runtime/types.d.ts" + ], + "@openclaw/acp-core/*": [ + "../../dist/plugin-sdk/packages/acp-core/src/*.d.ts" + ], "@openclaw/terminal-core": [ "../../dist/plugin-sdk/packages/terminal-core/src/index.d.ts" ], diff --git a/packages/acp-core/package.json b/packages/acp-core/package.json new file mode 100644 index 000000000000..7fb97fae6f41 --- /dev/null +++ b/packages/acp-core/package.json @@ -0,0 +1,39 @@ +{ + "name": "@openclaw/acp-core", + "version": "0.0.0-private", + "private": true, + "files": [ + "dist" + ], + "type": "module", + "main": "./dist/index.mjs", + "types": "./dist/index.d.mts", + "exports": { + ".": { + "types": "./dist/index.d.mts", + "import": "./dist/index.mjs", + "default": "./dist/index.mjs" + }, + "./normalize-text": { + "types": "./dist/normalize-text.d.mts", + "import": "./dist/normalize-text.mjs", + "default": "./dist/normalize-text.mjs" + }, + "./record-shared": { + "types": "./dist/record-shared.d.mts", + "import": "./dist/record-shared.mjs", + "default": "./dist/record-shared.mjs" + }, + "./runtime/types": { + "types": "./dist/runtime/types.d.mts", + "import": "./dist/runtime/types.mjs", + "default": "./dist/runtime/types.mjs" + } + }, + "dependencies": { + "@openclaw/normalization-core": "workspace:*" + }, + "scripts": { + "build": "tsdown src/index.ts src/normalize-text.ts src/record-shared.ts src/runtime/types.ts --no-config --platform node --format esm --dts --out-dir dist --clean" + } +} diff --git a/packages/acp-core/src/index.ts b/packages/acp-core/src/index.ts new file mode 100644 index 000000000000..b5e9a774fde5 --- /dev/null +++ b/packages/acp-core/src/index.ts @@ -0,0 +1,3 @@ +export * from "./normalize-text.js"; +export * from "./record-shared.js"; +export * from "./runtime/types.js"; diff --git a/packages/acp-core/src/normalize-text.ts b/packages/acp-core/src/normalize-text.ts new file mode 100644 index 000000000000..e74e66769f98 --- /dev/null +++ b/packages/acp-core/src/normalize-text.ts @@ -0,0 +1 @@ +export { normalizeOptionalString as normalizeText } from "@openclaw/normalization-core/string-coerce"; diff --git a/src/acp/record-shared.ts b/packages/acp-core/src/record-shared.ts similarity index 100% rename from src/acp/record-shared.ts rename to packages/acp-core/src/record-shared.ts diff --git a/src/acp/runtime/types.ts b/packages/acp-core/src/runtime/types.ts similarity index 100% rename from src/acp/runtime/types.ts rename to packages/acp-core/src/runtime/types.ts diff --git a/packages/media-core/package.json b/packages/media-core/package.json new file mode 100644 index 000000000000..d813bfb2f61f --- /dev/null +++ b/packages/media-core/package.json @@ -0,0 +1,75 @@ +{ + "name": "@openclaw/media-core", + "version": "0.0.0-private", + "private": true, + "files": [ + "dist" + ], + "type": "module", + "main": "./dist/index.mjs", + "types": "./dist/index.d.mts", + "exports": { + ".": { + "types": "./dist/index.d.mts", + "import": "./dist/index.mjs", + "default": "./dist/index.mjs" + }, + "./base64": { + "types": "./dist/base64.d.mts", + "import": "./dist/base64.mjs", + "default": "./dist/base64.mjs" + }, + "./constants": { + "types": "./dist/constants.d.mts", + "import": "./dist/constants.mjs", + "default": "./dist/constants.mjs" + }, + "./content-length": { + "types": "./dist/content-length.d.mts", + "import": "./dist/content-length.mjs", + "default": "./dist/content-length.mjs" + }, + "./file-name": { + "types": "./dist/file-name.d.mts", + "import": "./dist/file-name.mjs", + "default": "./dist/file-name.mjs" + }, + "./inbound-path-policy": { + "types": "./dist/inbound-path-policy.d.mts", + "import": "./dist/inbound-path-policy.mjs", + "default": "./dist/inbound-path-policy.mjs" + }, + "./inline-image-data-url": { + "types": "./dist/inline-image-data-url.d.mts", + "import": "./dist/inline-image-data-url.mjs", + "default": "./dist/inline-image-data-url.mjs" + }, + "./media-source-url": { + "types": "./dist/media-source-url.d.mts", + "import": "./dist/media-source-url.mjs", + "default": "./dist/media-source-url.mjs" + }, + "./mime": { + "types": "./dist/mime.d.mts", + "import": "./dist/mime.mjs", + "default": "./dist/mime.mjs" + }, + "./read-byte-stream-with-limit": { + "types": "./dist/read-byte-stream-with-limit.d.mts", + "import": "./dist/read-byte-stream-with-limit.mjs", + "default": "./dist/read-byte-stream-with-limit.mjs" + }, + "./read-response-with-limit": { + "types": "./dist/read-response-with-limit.d.mts", + "import": "./dist/read-response-with-limit.mjs", + "default": "./dist/read-response-with-limit.mjs" + } + }, + "dependencies": { + "@openclaw/normalization-core": "workspace:*", + "file-type": "22.0.1" + }, + "scripts": { + "build": "tsdown src/index.ts src/base64.ts src/constants.ts src/content-length.ts src/file-name.ts src/inbound-path-policy.ts src/inline-image-data-url.ts src/media-source-url.ts src/mime.ts src/read-byte-stream-with-limit.ts src/read-response-with-limit.ts --no-config --platform node --format esm --dts --out-dir dist --clean" + } +} diff --git a/src/media/base64.test.ts b/packages/media-core/src/base64.test.ts similarity index 100% rename from src/media/base64.test.ts rename to packages/media-core/src/base64.test.ts diff --git a/src/media/base64.ts b/packages/media-core/src/base64.ts similarity index 100% rename from src/media/base64.ts rename to packages/media-core/src/base64.ts diff --git a/src/media/constants.ts b/packages/media-core/src/constants.ts similarity index 100% rename from src/media/constants.ts rename to packages/media-core/src/constants.ts diff --git a/src/media/content-length.ts b/packages/media-core/src/content-length.ts similarity index 100% rename from src/media/content-length.ts rename to packages/media-core/src/content-length.ts diff --git a/src/media/file-name.ts b/packages/media-core/src/file-name.ts similarity index 100% rename from src/media/file-name.ts rename to packages/media-core/src/file-name.ts diff --git a/src/media/inbound-path-policy.test.ts b/packages/media-core/src/inbound-path-policy.test.ts similarity index 100% rename from src/media/inbound-path-policy.test.ts rename to packages/media-core/src/inbound-path-policy.test.ts diff --git a/src/media/inbound-path-policy.ts b/packages/media-core/src/inbound-path-policy.ts similarity index 100% rename from src/media/inbound-path-policy.ts rename to packages/media-core/src/inbound-path-policy.ts diff --git a/packages/media-core/src/index.ts b/packages/media-core/src/index.ts new file mode 100644 index 000000000000..b1c134644697 --- /dev/null +++ b/packages/media-core/src/index.ts @@ -0,0 +1,10 @@ +export * from "./base64.js"; +export * from "./constants.js"; +export * from "./content-length.js"; +export * from "./file-name.js"; +export * from "./inbound-path-policy.js"; +export * from "./inline-image-data-url.js"; +export * from "./media-source-url.js"; +export * from "./mime.js"; +export * from "./read-byte-stream-with-limit.js"; +export * from "./read-response-with-limit.js"; diff --git a/src/media/inline-image-data-url.test.ts b/packages/media-core/src/inline-image-data-url.test.ts similarity index 100% rename from src/media/inline-image-data-url.test.ts rename to packages/media-core/src/inline-image-data-url.test.ts diff --git a/src/media/inline-image-data-url.ts b/packages/media-core/src/inline-image-data-url.ts similarity index 100% rename from src/media/inline-image-data-url.ts rename to packages/media-core/src/inline-image-data-url.ts diff --git a/packages/media-core/src/lazy-import.ts b/packages/media-core/src/lazy-import.ts new file mode 100644 index 000000000000..558e2cdece33 --- /dev/null +++ b/packages/media-core/src/lazy-import.ts @@ -0,0 +1,37 @@ +export type LazyPromiseLoader = { + load(): Promise; + clear(): void; +}; + +export type LazyPromiseLoaderOptions = { + cacheRejections?: boolean; +}; + +export function createLazyImportLoader( + load: () => Promise, + options: LazyPromiseLoaderOptions = {}, +): LazyPromiseLoader { + let promise: Promise | undefined; + + const createPromise = (): Promise => { + const loaded = Promise.resolve().then(load); + if (options.cacheRejections !== true) { + void loaded.catch(() => { + if (promise === loaded) { + promise = undefined; + } + }); + } + return loaded; + }; + + return { + async load(): Promise { + promise ??= createPromise(); + return await promise; + }, + clear(): void { + promise = undefined; + }, + }; +} diff --git a/src/media/media-source-url.ts b/packages/media-core/src/media-source-url.ts similarity index 100% rename from src/media/media-source-url.ts rename to packages/media-core/src/media-source-url.ts diff --git a/src/media/mime.test.ts b/packages/media-core/src/mime.test.ts similarity index 100% rename from src/media/mime.test.ts rename to packages/media-core/src/mime.test.ts diff --git a/src/media/mime.ts b/packages/media-core/src/mime.ts similarity index 99% rename from src/media/mime.ts rename to packages/media-core/src/mime.ts index 28364b185b84..4bfc54361513 100644 --- a/src/media/mime.ts +++ b/packages/media-core/src/mime.ts @@ -1,6 +1,6 @@ import path from "node:path"; -import { createLazyImportLoader } from "../shared/lazy-promise.js"; import { type MediaKind, mediaKindFromMime } from "./constants.js"; +import { createLazyImportLoader } from "./lazy-import.js"; /** @internal */ export const FILE_TYPE_SNIFF_MAX_BYTES = 1024 * 1024; diff --git a/src/media/read-byte-stream-with-limit.test.ts b/packages/media-core/src/read-byte-stream-with-limit.test.ts similarity index 100% rename from src/media/read-byte-stream-with-limit.test.ts rename to packages/media-core/src/read-byte-stream-with-limit.test.ts diff --git a/src/media/read-byte-stream-with-limit.ts b/packages/media-core/src/read-byte-stream-with-limit.ts similarity index 100% rename from src/media/read-byte-stream-with-limit.ts rename to packages/media-core/src/read-byte-stream-with-limit.ts diff --git a/src/media/read-response-with-limit.test.ts b/packages/media-core/src/read-response-with-limit.test.ts similarity index 98% rename from src/media/read-response-with-limit.test.ts rename to packages/media-core/src/read-response-with-limit.test.ts index 4051ecd4a980..59ff58732a6c 100644 --- a/src/media/read-response-with-limit.test.ts +++ b/packages/media-core/src/read-response-with-limit.test.ts @@ -1,5 +1,5 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; -import { MAX_TIMER_TIMEOUT_MS } from "../shared/number-coercion.js"; +import { MAX_TIMER_TIMEOUT_MS } from "@openclaw/normalization-core/number-coercion"; import { readResponseTextSnippet, readResponseWithLimit } from "./read-response-with-limit.js"; function makeStream(chunks: Uint8Array[], delayMs?: number) { diff --git a/src/media/read-response-with-limit.ts b/packages/media-core/src/read-response-with-limit.ts similarity index 98% rename from src/media/read-response-with-limit.ts rename to packages/media-core/src/read-response-with-limit.ts index 10943a0f08db..604ae6c30c8e 100644 --- a/src/media/read-response-with-limit.ts +++ b/packages/media-core/src/read-response-with-limit.ts @@ -1,4 +1,4 @@ -import { resolveTimerTimeoutMs } from "../shared/number-coercion.js"; +import { resolveTimerTimeoutMs } from "@openclaw/normalization-core/number-coercion"; async function readChunkWithIdleTimeout( reader: ReadableStreamDefaultReader, diff --git a/packages/memory-host-sdk/src/host/openclaw-runtime.ts b/packages/memory-host-sdk/src/host/openclaw-runtime.ts index 4ff81ed404d2..02800a730e08 100644 --- a/packages/memory-host-sdk/src/host/openclaw-runtime.ts +++ b/packages/memory-host-sdk/src/host/openclaw-runtime.ts @@ -94,7 +94,7 @@ export { export type { ProcessWarning } from "../../../../src/infra/warning-filter.js"; export { redactSensitiveText } from "../../../../src/logging/redact.js"; export { createSubsystemLogger } from "../../../../src/logging/subsystem.js"; -export { detectMime } from "../../../../src/media/mime.js"; +export { detectMime } from "@openclaw/media-core/mime"; // Memory plugin helpers. export { diff --git a/packages/plugin-sdk/tsconfig.json b/packages/plugin-sdk/tsconfig.json index 69cb87f11494..669ca6ae5c46 100644 --- a/packages/plugin-sdk/tsconfig.json +++ b/packages/plugin-sdk/tsconfig.json @@ -14,9 +14,11 @@ }, "include": [ "../../packages/markdown-core/src/**/*.ts", + "../../packages/media-core/src/**/*.ts", "../../packages/media-generation-core/src/**/*.ts", "../../packages/model-catalog-core/src/**/*.ts", "../../packages/normalization-core/src/**/*.ts", + "../../packages/acp-core/src/**/*.ts", "../../packages/terminal-core/src/**/*.ts", "../../src/plugin-sdk/**/*.ts", "../../src/video-generation/dashscope-compatible.ts", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2486b676b904..054bdc637dac 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1770,6 +1770,12 @@ importers: specifier: 2026.5.28 version: 2026.5.28 + packages/acp-core: + dependencies: + '@openclaw/normalization-core': + specifier: workspace:* + version: link:../normalization-core + packages/agent-core: dependencies: '@openclaw/llm-core': @@ -1824,6 +1830,15 @@ importers: specifier: 2.9.0 version: 2.9.0 + packages/media-core: + dependencies: + '@openclaw/normalization-core': + specifier: workspace:* + version: link:../normalization-core + file-type: + specifier: 22.0.1 + version: 22.0.1 + packages/media-generation-core: {} packages/media-understanding-common: {} @@ -1877,6 +1892,9 @@ importers: '@noble/ed25519': specifier: 3.1.0 version: 3.1.0 + '@openclaw/media-core': + specifier: workspace:* + version: link:../packages/media-core '@openclaw/normalization-core': specifier: workspace:* version: link:../packages/normalization-core diff --git a/scripts/build-all.mjs b/scripts/build-all.mjs index 5aa74f387ad1..09764a6b1c86 100644 --- a/scripts/build-all.mjs +++ b/scripts/build-all.mjs @@ -48,8 +48,10 @@ export const BUILD_ALL_STEPS = [ "packages/plugin-sdk/package.json", "packages/llm-core/package.json", "packages/markdown-core/package.json", + "packages/media-core/package.json", "packages/media-understanding-common/package.json", "packages/terminal-core/package.json", + "packages/acp-core/package.json", "packages/model-catalog-core/package.json", "packages/normalization-core/package.json", "packages/web-content-core/package.json", @@ -59,10 +61,12 @@ export const BUILD_ALL_STEPS = [ "src/plugin-sdk", "packages/llm-core/src", "packages/markdown-core/src", + "packages/media-core/src", "packages/model-catalog-core/src", "packages/memory-host-sdk/src", "packages/media-generation-core/src", "packages/normalization-core/src", + "packages/acp-core/src", "packages/media-understanding-common/src", "packages/terminal-core/src", "packages/web-content-core/src", diff --git a/scripts/lib/extension-package-boundary.ts b/scripts/lib/extension-package-boundary.ts index 38855c311a96..f7936ea80599 100644 --- a/scripts/lib/extension-package-boundary.ts +++ b/scripts/lib/extension-package-boundary.ts @@ -116,6 +116,48 @@ export const EXTENSION_PACKAGE_BOUNDARY_BASE_PATHS = { "@openclaw/media-generation-core/*": [ "../dist/plugin-sdk/packages/media-generation-core/src/*.d.ts", ], + "@openclaw/media-core": ["../dist/plugin-sdk/packages/media-core/src/index.d.ts"], + "@openclaw/media-core/base64": ["../dist/plugin-sdk/packages/media-core/src/base64.d.ts"], + "@openclaw/media-core/constants": ["../dist/plugin-sdk/packages/media-core/src/constants.d.ts"], + "@openclaw/media-core/content-length": [ + "../dist/plugin-sdk/packages/media-core/src/content-length.d.ts", + ], + "@openclaw/media-core/file-name": ["../dist/plugin-sdk/packages/media-core/src/file-name.d.ts"], + "@openclaw/media-core/inbound-path-policy": [ + "../dist/plugin-sdk/packages/media-core/src/inbound-path-policy.d.ts", + ], + "@openclaw/media-core/inline-image-data-url": [ + "../dist/plugin-sdk/packages/media-core/src/inline-image-data-url.d.ts", + ], + "@openclaw/media-core/media-source-url": [ + "../dist/plugin-sdk/packages/media-core/src/media-source-url.d.ts", + ], + "@openclaw/media-core/mime": ["../dist/plugin-sdk/packages/media-core/src/mime.d.ts"], + "@openclaw/media-core/read-byte-stream-with-limit": [ + "../dist/plugin-sdk/packages/media-core/src/read-byte-stream-with-limit.d.ts", + ], + "@openclaw/media-core/read-response-with-limit": [ + "../dist/plugin-sdk/packages/media-core/src/read-response-with-limit.d.ts", + ], + "@openclaw/media-core/*": ["../dist/plugin-sdk/packages/media-core/src/*.d.ts"], + "@openclaw/normalization-core/record-coerce": [ + "../dist/plugin-sdk/packages/normalization-core/src/record-coerce.d.ts", + ], + "@openclaw/normalization-core/string-coerce": [ + "../dist/plugin-sdk/packages/normalization-core/src/string-coerce.d.ts", + ], + "@openclaw/normalization-core/*": ["../dist/plugin-sdk/packages/normalization-core/src/*.d.ts"], + "@openclaw/acp-core": ["../dist/plugin-sdk/packages/acp-core/src/index.d.ts"], + "@openclaw/acp-core/normalize-text": [ + "../dist/plugin-sdk/packages/acp-core/src/normalize-text.d.ts", + ], + "@openclaw/acp-core/record-shared": [ + "../dist/plugin-sdk/packages/acp-core/src/record-shared.d.ts", + ], + "@openclaw/acp-core/runtime/types": [ + "../dist/plugin-sdk/packages/acp-core/src/runtime/types.d.ts", + ], + "@openclaw/acp-core/*": ["../dist/plugin-sdk/packages/acp-core/src/*.d.ts"], "@openclaw/terminal-core": ["../dist/plugin-sdk/packages/terminal-core/src/index.d.ts"], "@openclaw/terminal-core/ansi": ["../dist/plugin-sdk/packages/terminal-core/src/ansi.d.ts"], "@openclaw/terminal-core/decorative-emoji": [ diff --git a/scripts/lib/tsdown-output-roots.mjs b/scripts/lib/tsdown-output-roots.mjs index aac28dfe4452..d1a66e32dcbd 100644 --- a/scripts/lib/tsdown-output-roots.mjs +++ b/scripts/lib/tsdown-output-roots.mjs @@ -5,6 +5,7 @@ const TSDOWN_PACKAGE_NAMES = [ "llm-core", "llm-runtime", "markdown-core", + "media-core", "media-generation-core", "media-understanding-common", "model-catalog-core", @@ -12,6 +13,7 @@ const TSDOWN_PACKAGE_NAMES = [ "normalization-core", "speech-core", "terminal-core", + "acp-core", ]; export const TSDOWN_PACKAGE_OUTPUT_ROOTS = TSDOWN_PACKAGE_NAMES.map(packageOutputRoot); diff --git a/scripts/prepare-extension-package-boundary-artifacts.mjs b/scripts/prepare-extension-package-boundary-artifacts.mjs index c123084d036a..1b4610e09980 100644 --- a/scripts/prepare-extension-package-boundary-artifacts.mjs +++ b/scripts/prepare-extension-package-boundary-artifacts.mjs @@ -17,11 +17,13 @@ const PLUGIN_SDK_TYPE_INPUTS = [ "src/auto-reply", "packages/llm-core/src", "packages/markdown-core/src", + "packages/media-core/src", "packages/model-catalog-core/src", "packages/memory-host-sdk/src", "packages/media-generation-core/src", "packages/media-understanding-common/src", "packages/normalization-core/src", + "packages/acp-core/src", "packages/terminal-core/src", "src/video-generation/dashscope-compatible.ts", "src/video-generation/types.ts", @@ -52,6 +54,17 @@ const ROOT_DTS_REQUIRED_OUTPUTS = [ "dist/plugin-sdk/packages/media-generation-core/src/index.d.ts", "dist/plugin-sdk/packages/media-generation-core/src/model-ref.d.ts", "dist/plugin-sdk/packages/media-generation-core/src/normalization.d.ts", + "dist/plugin-sdk/packages/media-core/src/base64.d.ts", + "dist/plugin-sdk/packages/media-core/src/constants.d.ts", + "dist/plugin-sdk/packages/media-core/src/content-length.d.ts", + "dist/plugin-sdk/packages/media-core/src/file-name.d.ts", + "dist/plugin-sdk/packages/media-core/src/inbound-path-policy.d.ts", + "dist/plugin-sdk/packages/media-core/src/inline-image-data-url.d.ts", + "dist/plugin-sdk/packages/media-core/src/media-source-url.d.ts", + "dist/plugin-sdk/packages/media-core/src/mime.d.ts", + "dist/plugin-sdk/packages/media-core/src/read-byte-stream-with-limit.d.ts", + "dist/plugin-sdk/packages/media-core/src/read-response-with-limit.d.ts", + "dist/plugin-sdk/packages/acp-core/src/runtime/types.d.ts", "dist/plugin-sdk/packages/terminal-core/src/ansi.d.ts", "dist/plugin-sdk/packages/terminal-core/src/decorative-emoji.d.ts", "dist/plugin-sdk/packages/terminal-core/src/health-style.d.ts", @@ -99,6 +112,17 @@ const PACKAGE_DTS_REQUIRED_OUTPUTS = [ "packages/plugin-sdk/dist/packages/media-generation-core/src/index.d.ts", "packages/plugin-sdk/dist/packages/media-generation-core/src/model-ref.d.ts", "packages/plugin-sdk/dist/packages/media-generation-core/src/normalization.d.ts", + "packages/plugin-sdk/dist/packages/media-core/src/base64.d.ts", + "packages/plugin-sdk/dist/packages/media-core/src/constants.d.ts", + "packages/plugin-sdk/dist/packages/media-core/src/content-length.d.ts", + "packages/plugin-sdk/dist/packages/media-core/src/file-name.d.ts", + "packages/plugin-sdk/dist/packages/media-core/src/inbound-path-policy.d.ts", + "packages/plugin-sdk/dist/packages/media-core/src/inline-image-data-url.d.ts", + "packages/plugin-sdk/dist/packages/media-core/src/media-source-url.d.ts", + "packages/plugin-sdk/dist/packages/media-core/src/mime.d.ts", + "packages/plugin-sdk/dist/packages/media-core/src/read-byte-stream-with-limit.d.ts", + "packages/plugin-sdk/dist/packages/media-core/src/read-response-with-limit.d.ts", + "packages/plugin-sdk/dist/packages/acp-core/src/runtime/types.d.ts", "packages/plugin-sdk/dist/packages/model-catalog-core/src/configured-model-refs.d.ts", "packages/plugin-sdk/dist/packages/model-catalog-core/src/model-catalog-normalize.d.ts", "packages/plugin-sdk/dist/packages/model-catalog-core/src/model-catalog-refs.d.ts", diff --git a/scripts/run-node-watch-paths.mjs b/scripts/run-node-watch-paths.mjs index 17d946c77aed..d1364ca8790f 100644 --- a/scripts/run-node-watch-paths.mjs +++ b/scripts/run-node-watch-paths.mjs @@ -11,9 +11,11 @@ const RUN_NODE_PACKAGE_SOURCE_ROOTS = [ "packages/gateway-client/src", "packages/gateway-protocol/src", "packages/markdown-core/src", + "packages/media-core/src", "packages/media-generation-core/src", "packages/media-understanding-common/src", "packages/normalization-core/src", + "packages/acp-core/src", "packages/terminal-core/src", "packages/web-content-core/src", "packages/net-policy/src", diff --git a/scripts/write-plugin-sdk-entry-dts.ts b/scripts/write-plugin-sdk-entry-dts.ts index 2a4f44f2ba34..4034b6096071 100644 --- a/scripts/write-plugin-sdk-entry-dts.ts +++ b/scripts/write-plugin-sdk-entry-dts.ts @@ -46,7 +46,9 @@ const RUNTIME_SHIMS: Partial> = { function isBareImportSpecifier(id: string): boolean { if ( id === "@openclaw/model-catalog-core/model-catalog-types" || - id.startsWith("@openclaw/normalization-core/") + id.startsWith("@openclaw/normalization-core/") || + id.startsWith("@openclaw/media-core/") || + id.startsWith("@openclaw/acp-core/") ) { return false; } diff --git a/src/acp/approval-classifier.ts b/src/acp/approval-classifier.ts index 1af263fdc7eb..95b40f2ffc0e 100644 --- a/src/acp/approval-classifier.ts +++ b/src/acp/approval-classifier.ts @@ -1,5 +1,6 @@ import { homedir } from "node:os"; import path from "node:path"; +import { asRecord } from "@openclaw/acp-core/record-shared"; import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, @@ -7,7 +8,6 @@ import { import { isKnownCoreToolId } from "../agents/tool-catalog.js"; import { isMutatingToolCall } from "../agents/tool-mutation.js"; import { isPathInside } from "../infra/path-guards.js"; -import { asRecord } from "./record-shared.js"; const SAFE_SEARCH_TOOL_IDS = new Set(["search", "web_search", "memory_search"]); const TRUSTED_SAFE_TOOL_ALIASES = new Set(["search"]); diff --git a/src/acp/control-plane/manager.core.ts b/src/acp/control-plane/manager.core.ts index c3e36099b3f5..357102e40a86 100644 --- a/src/acp/control-plane/manager.core.ts +++ b/src/acp/control-plane/manager.core.ts @@ -1,3 +1,10 @@ +import type { + AcpRuntime, + AcpRuntimeCapabilities, + AcpRuntimeHandle, + AcpRuntimeSessionMode, + AcpRuntimeStatus, +} from "@openclaw/acp-core/runtime/types"; import { clampTimerTimeoutMs } from "@openclaw/normalization-core/number-coercion"; import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce"; import { resolveAgentTimeoutMs } from "../../agents/timeout.js"; @@ -32,13 +39,6 @@ import { resolveRuntimeHandleIdentifiersFromIdentity, resolveSessionIdentityFromMeta, } from "../runtime/session-identity.js"; -import type { - AcpRuntime, - AcpRuntimeCapabilities, - AcpRuntimeHandle, - AcpRuntimeSessionMode, - AcpRuntimeStatus, -} from "../runtime/types.js"; import { reconcileManagerRuntimeSessionIdentifiers } from "./manager.identity-reconcile.js"; import { applyManagerRuntimeControls, diff --git a/src/acp/control-plane/manager.identity-reconcile.ts b/src/acp/control-plane/manager.identity-reconcile.ts index 0e5d8f1794d6..c7a7ebbb512e 100644 --- a/src/acp/control-plane/manager.identity-reconcile.ts +++ b/src/acp/control-plane/manager.identity-reconcile.ts @@ -1,3 +1,8 @@ +import type { + AcpRuntime, + AcpRuntimeHandle, + AcpRuntimeStatus, +} from "@openclaw/acp-core/runtime/types"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { logVerbose } from "../../globals.js"; import { withAcpRuntimeErrorBoundary } from "../runtime/errors.js"; @@ -9,7 +14,6 @@ import { resolveRuntimeHandleIdentifiersFromIdentity, resolveSessionIdentityFromMeta, } from "../runtime/session-identity.js"; -import type { AcpRuntime, AcpRuntimeHandle, AcpRuntimeStatus } from "../runtime/types.js"; import type { SessionAcpMeta, SessionEntry } from "./manager.types.js"; import { hasLegacyAcpIdentityProjection } from "./manager.utils.js"; diff --git a/src/acp/control-plane/manager.runtime-controls.ts b/src/acp/control-plane/manager.runtime-controls.ts index fcbd37b325e4..1e1757866d22 100644 --- a/src/acp/control-plane/manager.runtime-controls.ts +++ b/src/acp/control-plane/manager.runtime-controls.ts @@ -1,12 +1,12 @@ -import { asNullableRecord } from "@openclaw/normalization-core/record-coerce"; -import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce"; -import { AcpRuntimeError, withAcpRuntimeErrorBoundary } from "../runtime/errors.js"; import type { AcpRuntime, AcpRuntimeCapabilities, AcpRuntimeHandle, AcpRuntimeStatus, -} from "../runtime/types.js"; +} from "@openclaw/acp-core/runtime/types"; +import { asNullableRecord } from "@openclaw/normalization-core/record-coerce"; +import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce"; +import { AcpRuntimeError, withAcpRuntimeErrorBoundary } from "../runtime/errors.js"; import type { SessionAcpMeta } from "./manager.types.js"; import { createUnsupportedControlError } from "./manager.utils.js"; import type { CachedRuntimeState } from "./runtime-cache.js"; diff --git a/src/acp/control-plane/manager.test.ts b/src/acp/control-plane/manager.test.ts index b9f5e2bcd167..5b0e7df8e4cd 100644 --- a/src/acp/control-plane/manager.test.ts +++ b/src/acp/control-plane/manager.test.ts @@ -1,12 +1,12 @@ import { setTimeout as scheduleNativeTimeout } from "node:timers"; import { setTimeout as sleep } from "node:timers/promises"; +import type { AcpRuntime, AcpRuntimeCapabilities } from "@openclaw/acp-core/runtime/types"; import { MAX_TIMER_TIMEOUT_MS } from "@openclaw/normalization-core/number-coercion"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../../config/config.js"; import type { AcpSessionRuntimeOptions, SessionAcpMeta } from "../../config/sessions/types.js"; import { resetHeartbeatWakeStateForTests } from "../../infra/heartbeat-wake.js"; import { withTempDir } from "../../test-helpers/temp-dir.js"; -import type { AcpRuntime, AcpRuntimeCapabilities } from "../runtime/types.js"; const hoisted = vi.hoisted(() => { const listAcpSessionEntriesMock = vi.fn(); diff --git a/src/acp/control-plane/manager.turn-stream.ts b/src/acp/control-plane/manager.turn-stream.ts index 7e252d89fff2..aba46e31b71a 100644 --- a/src/acp/control-plane/manager.turn-stream.ts +++ b/src/acp/control-plane/manager.turn-stream.ts @@ -1,10 +1,10 @@ -import { AcpRuntimeError } from "../runtime/errors.js"; import type { AcpRuntime, AcpRuntimeEvent, AcpRuntimeTurnInput, AcpRuntimeTurnResult, -} from "../runtime/types.js"; +} from "@openclaw/acp-core/runtime/types"; +import { AcpRuntimeError } from "../runtime/errors.js"; import { normalizeAcpErrorCode } from "./manager.utils.js"; import { normalizeText } from "./runtime-options.js"; diff --git a/src/acp/control-plane/manager.types.ts b/src/acp/control-plane/manager.types.ts index 2afebb5cf05d..f3fba6164aee 100644 --- a/src/acp/control-plane/manager.types.ts +++ b/src/acp/control-plane/manager.types.ts @@ -1,3 +1,12 @@ +import type { + AcpRuntime, + AcpRuntimeCapabilities, + AcpRuntimeEvent, + AcpRuntimeHandle, + AcpRuntimePromptMode, + AcpRuntimeSessionMode, + AcpRuntimeStatus, +} from "@openclaw/acp-core/runtime/types"; import type { SessionAcpIdentity, AcpSessionRuntimeOptions, @@ -12,15 +21,6 @@ import { readAcpSessionEntry, upsertAcpSessionMeta, } from "../runtime/session-meta.js"; -import type { - AcpRuntime, - AcpRuntimeCapabilities, - AcpRuntimeEvent, - AcpRuntimeHandle, - AcpRuntimePromptMode, - AcpRuntimeSessionMode, - AcpRuntimeStatus, -} from "../runtime/types.js"; export type AcpSessionResolution = | { diff --git a/src/acp/control-plane/runtime-cache.test.ts b/src/acp/control-plane/runtime-cache.test.ts index 44c48bb51449..b4a42ff4c627 100644 --- a/src/acp/control-plane/runtime-cache.test.ts +++ b/src/acp/control-plane/runtime-cache.test.ts @@ -1,6 +1,6 @@ +import type { AcpRuntime } from "@openclaw/acp-core/runtime/types"; +import type { AcpRuntimeHandle } from "@openclaw/acp-core/runtime/types"; import { describe, expect, it } from "vitest"; -import type { AcpRuntime } from "../runtime/types.js"; -import type { AcpRuntimeHandle } from "../runtime/types.js"; import type { CachedRuntimeState } from "./runtime-cache.js"; import { RuntimeCache } from "./runtime-cache.js"; diff --git a/src/acp/control-plane/runtime-cache.ts b/src/acp/control-plane/runtime-cache.ts index 1e34c6ae1a7a..9c2ed087c7ae 100644 --- a/src/acp/control-plane/runtime-cache.ts +++ b/src/acp/control-plane/runtime-cache.ts @@ -1,4 +1,8 @@ -import type { AcpRuntime, AcpRuntimeHandle, AcpRuntimeSessionMode } from "../runtime/types.js"; +import type { + AcpRuntime, + AcpRuntimeHandle, + AcpRuntimeSessionMode, +} from "@openclaw/acp-core/runtime/types"; export type CachedRuntimeState = { runtime: AcpRuntime; diff --git a/src/acp/control-plane/runtime-options.ts b/src/acp/control-plane/runtime-options.ts index 516977882ee0..46d119af3fe6 100644 --- a/src/acp/control-plane/runtime-options.ts +++ b/src/acp/control-plane/runtime-options.ts @@ -1,11 +1,11 @@ import { isAbsolute } from "node:path"; +import { normalizeText } from "@openclaw/acp-core/normalize-text"; import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce"; import type { AcpSessionRuntimeOptions, SessionAcpMeta } from "../../config/sessions/types.js"; import { parseStrictPositiveInteger } from "../../infra/parse-finite-number.js"; -import { normalizeText } from "../normalize-text.js"; import { AcpRuntimeError } from "../runtime/errors.js"; -export { normalizeText } from "../normalize-text.js"; +export { normalizeText } from "@openclaw/acp-core/normalize-text"; const MAX_RUNTIME_MODE_LENGTH = 64; const MAX_MODEL_LENGTH = 200; diff --git a/src/acp/event-mapper.ts b/src/acp/event-mapper.ts index cddfa427f3d0..f0e4f62e59d1 100644 --- a/src/acp/event-mapper.ts +++ b/src/acp/event-mapper.ts @@ -5,13 +5,13 @@ import type { ToolCallLocation, ToolKind, } from "@agentclientprotocol/sdk"; +import { asRecord } from "@openclaw/acp-core/record-shared"; import { hasNonEmptyString, normalizeLowercaseStringOrEmpty, normalizeOptionalString, readStringValue, } from "@openclaw/normalization-core/string-coerce"; -import { asRecord } from "./record-shared.js"; type GatewayAttachment = { type: string; diff --git a/src/acp/normalize-text.ts b/src/acp/normalize-text.ts deleted file mode 100644 index 94ddf9374749..000000000000 --- a/src/acp/normalize-text.ts +++ /dev/null @@ -1 +0,0 @@ -export { normalizeOptionalString as normalizeText } from "../../packages/normalization-core/src/string-coerce.js"; diff --git a/src/acp/persistent-bindings.types.ts b/src/acp/persistent-bindings.types.ts index 3a1ffb9fd844..b8632307bda6 100644 --- a/src/acp/persistent-bindings.types.ts +++ b/src/acp/persistent-bindings.types.ts @@ -1,13 +1,13 @@ import { createHash } from "node:crypto"; +import { normalizeText } from "@openclaw/acp-core/normalize-text"; +import type { AcpRuntimeSessionMode } from "@openclaw/acp-core/runtime/types"; import { normalizeOptionalLowercaseString } from "@openclaw/normalization-core/string-coerce"; import type { ChannelId } from "../channels/plugins/types.public.js"; import type { SessionBindingRecord } from "../infra/outbound/session-binding-service.js"; import { normalizeAccountId, resolveAgentIdFromSessionKey } from "../routing/session-key.js"; import { sanitizeAgentId } from "../routing/session-key.js"; -import { normalizeText } from "./normalize-text.js"; -import type { AcpRuntimeSessionMode } from "./runtime/types.js"; -export { normalizeText } from "./normalize-text.js"; +export { normalizeText } from "@openclaw/acp-core/normalize-text"; export type ConfiguredAcpBindingChannel = ChannelId; diff --git a/src/acp/runtime/adapter-contract.testkit.ts b/src/acp/runtime/adapter-contract.testkit.ts index b7f8db301ecb..6d6018aa1156 100644 --- a/src/acp/runtime/adapter-contract.testkit.ts +++ b/src/acp/runtime/adapter-contract.testkit.ts @@ -1,8 +1,8 @@ import { randomUUID } from "node:crypto"; +import type { AcpRuntime, AcpRuntimeEvent } from "@openclaw/acp-core/runtime/types"; import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; import { expect } from "vitest"; import { toAcpRuntimeError } from "./errors.js"; -import type { AcpRuntime, AcpRuntimeEvent } from "./types.js"; export type AcpRuntimeAdapterContractParams = { createRuntime: () => Promise | AcpRuntime; diff --git a/src/acp/runtime/registry.test.ts b/src/acp/runtime/registry.test.ts index a29f6265059d..22a53a622ab1 100644 --- a/src/acp/runtime/registry.test.ts +++ b/src/acp/runtime/registry.test.ts @@ -1,3 +1,4 @@ +import type { AcpRuntime } from "@openclaw/acp-core/runtime/types"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { AcpRuntimeError } from "./errors.js"; import { @@ -7,7 +8,6 @@ import { requireAcpRuntimeBackend, unregisterAcpRuntimeBackend, } from "./registry.js"; -import type { AcpRuntime } from "./types.js"; function createRuntimeStub(): AcpRuntime { return { diff --git a/src/acp/runtime/registry.ts b/src/acp/runtime/registry.ts index 1e77b799e4ec..8f406f9e2955 100644 --- a/src/acp/runtime/registry.ts +++ b/src/acp/runtime/registry.ts @@ -1,7 +1,7 @@ +import type { AcpRuntime } from "@openclaw/acp-core/runtime/types"; import { normalizeOptionalLowercaseString } from "@openclaw/normalization-core/string-coerce"; import { resolveGlobalSingleton } from "../../shared/global-singleton.js"; import { AcpRuntimeError } from "./errors.js"; -import type { AcpRuntime } from "./types.js"; export type AcpRuntimeBackend = { id: string; diff --git a/src/acp/runtime/session-identifiers.ts b/src/acp/runtime/session-identifiers.ts index 82181e3c25fe..444c21500316 100644 --- a/src/acp/runtime/session-identifiers.ts +++ b/src/acp/runtime/session-identifiers.ts @@ -1,6 +1,6 @@ +import { normalizeText } from "@openclaw/acp-core/normalize-text"; import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce"; import type { SessionAcpIdentity, SessionAcpMeta } from "../../config/sessions/types.js"; -import { normalizeText } from "../normalize-text.js"; import { isSessionIdentityPending, resolveSessionIdentityFromMeta } from "./session-identity.js"; export const ACP_SESSION_IDENTITY_RENDERER_VERSION = "v1"; diff --git a/src/acp/runtime/session-identity.ts b/src/acp/runtime/session-identity.ts index 16f51be9756f..046b67be8a46 100644 --- a/src/acp/runtime/session-identity.ts +++ b/src/acp/runtime/session-identity.ts @@ -1,10 +1,10 @@ +import { normalizeText } from "@openclaw/acp-core/normalize-text"; +import type { AcpRuntimeHandle, AcpRuntimeStatus } from "@openclaw/acp-core/runtime/types"; import type { SessionAcpIdentity, SessionAcpIdentitySource, SessionAcpMeta, } from "../../config/sessions/types.js"; -import { normalizeText } from "../normalize-text.js"; -import type { AcpRuntimeHandle, AcpRuntimeStatus } from "./types.js"; function normalizeIdentityState(value: unknown): SessionAcpIdentity["state"] | undefined { if (value !== "pending" && value !== "resolved") { diff --git a/src/agents/acp-spawn.ts b/src/agents/acp-spawn.ts index 9992b572b50f..f14e252c0a7a 100644 --- a/src/agents/acp-spawn.ts +++ b/src/agents/acp-spawn.ts @@ -1,5 +1,6 @@ import crypto from "node:crypto"; import fs from "node:fs/promises"; +import type { AcpRuntimeSessionMode } from "@openclaw/acp-core/runtime/types"; import { normalizeOptionalLowercaseString, normalizeOptionalString, @@ -15,7 +16,6 @@ import { resolveAcpSessionCwd, resolveAcpThreadSessionDetailLines, } from "../acp/runtime/session-identifiers.js"; -import type { AcpRuntimeSessionMode } from "../acp/runtime/types.js"; import { DEFAULT_HEARTBEAT_EVERY } from "../auto-reply/heartbeat.js"; import { resolveChannelDefaultBindingPlacement, diff --git a/src/agents/agent-tools.read.ts b/src/agents/agent-tools.read.ts index f6f78a471d74..6d50ca609f96 100644 --- a/src/agents/agent-tools.read.ts +++ b/src/agents/agent-tools.read.ts @@ -1,6 +1,7 @@ import fs from "node:fs/promises"; import path from "node:path"; import { URL } from "node:url"; +import { detectMime } from "@openclaw/media-core/mime"; import { isWindowsDrivePath } from "../infra/archive-path.js"; import { canonicalPathFromExistingAncestor, @@ -9,7 +10,6 @@ import { } from "../infra/fs-safe.js"; import { expandHomePrefix, resolveOsHomeDir } from "../infra/home-dir.js"; import { hasEncodedFileUrlSeparator, trySafeFileURLToPath } from "../infra/local-file-access.js"; -import { detectMime } from "../media/mime.js"; import { sniffMimeFromBase64 } from "../media/sniff-mime-from-base64.js"; import { REQUIRED_PARAM_GROUPS, diff --git a/src/agents/bash-tools.exec-foreground-failures.test.ts b/src/agents/bash-tools.exec-foreground-failures.test.ts index 1517180e29dc..22dbc26b3dca 100644 --- a/src/agents/bash-tools.exec-foreground-failures.test.ts +++ b/src/agents/bash-tools.exec-foreground-failures.test.ts @@ -22,6 +22,27 @@ const defaultShell = isWin ? undefined : process.env.OPENCLAW_TEST_SHELL || resolveShellFromPath("bash") || process.env.SHELL || "sh"; +function requireTextContent( + result: Awaited["execute"]>>, +) { + const content = result.content[0]; + expect(content?.type).toBe("text"); + if (content?.type !== "text") { + throw new Error(`expected text content, got ${content?.type ?? "missing"}`); + } + return content.text; +} + +function requireFailedDetails( + details: Awaited["execute"]>>["details"], +) { + expect(details.status).toBe("failed"); + if (details.status !== "failed") { + throw new Error(`expected failed details, got ${details.status}`); + } + return details; +} + describe("exec foreground failures", () => { let envSnapshot: ReturnType | undefined; @@ -49,7 +70,7 @@ describe("exec foreground failures", () => { const tool = createExecTool({ security: "full", ask: "off", - timeoutSec: 0.05, + timeoutSec: 1, backgroundMs: 10, allowBackground: false, }); @@ -81,16 +102,11 @@ describe("exec foreground failures", () => { }); expect(supervisorMock.spawn).toHaveBeenCalledOnce(); - expect((supervisorMock.spawn.mock.calls[0]?.[0] as SpawnInput | undefined)?.timeoutMs).toBe(50); - expect(result.content[0]?.type).toBe("text"); - const details = result.details as { - status?: string; - exitCode?: number | null; - aggregated?: string; - durationMs?: number; - timedOut?: boolean; - }; - expect(details.status).toBe("failed"); + expect(supervisorMock.spawn.mock.calls[0]?.[0]?.timeoutMs).toBe(1_000); + const text = requireTextContent(result); + expect(text).toMatch(/timed out/i); + expect(text).toMatch(/re-run with a higher timeout/i); + const details = requireFailedDetails(result.details); expect(details.exitCode).toBeNull(); expect(details.timedOut).toBe(true); expect(details.aggregated).toBe(""); diff --git a/src/agents/cli-runner.helpers.test.ts b/src/agents/cli-runner.helpers.test.ts index bd010eb9fe21..db199ff841ce 100644 --- a/src/agents/cli-runner.helpers.test.ts +++ b/src/agents/cli-runner.helpers.test.ts @@ -1,10 +1,10 @@ import fs from "node:fs/promises"; import path from "node:path"; +import { MAX_IMAGE_BYTES } from "@openclaw/media-core/constants"; import type { ImageContent } from "openclaw/plugin-sdk/llm"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { createSolidPngBuffer } from "../../test/helpers/image-fixtures.js"; import { resolvePreferredOpenClawTmpDir } from "../infra/tmp-openclaw-dir.js"; -import { MAX_IMAGE_BYTES } from "../media/constants.js"; import { escapeRegExp } from "../shared/regexp.js"; import { buildCliArgs, diff --git a/src/agents/cli-runner/helpers.ts b/src/agents/cli-runner/helpers.ts index 7dab057c42b8..9c8fd472b264 100644 --- a/src/agents/cli-runner/helpers.ts +++ b/src/agents/cli-runner/helpers.ts @@ -2,6 +2,8 @@ import crypto from "node:crypto"; import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; +import { MAX_IMAGE_BYTES } from "@openclaw/media-core/constants"; +import { extensionForMime } from "@openclaw/media-core/mime"; import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, @@ -16,8 +18,6 @@ import { privateFileStore } from "../../infra/private-file-store.js"; import { tempWorkspace } from "../../infra/private-temp-workspace.js"; import { resolvePreferredOpenClawTmpDir } from "../../infra/tmp-openclaw-dir.js"; import type { ImageContent } from "../../llm/types.js"; -import { MAX_IMAGE_BYTES } from "../../media/constants.js"; -import { extensionForMime } from "../../media/mime.js"; import { listRegisteredPluginAgentPromptGuidance } from "../../plugins/command-registry-state.js"; import type { EmbeddedContextFile } from "../embedded-agent-helpers.js"; import { detectImageReferences, loadImageFromRef } from "../embedded-agent-runner/run/images.js"; diff --git a/src/agents/command/attempt-execution.ts b/src/agents/command/attempt-execution.ts index 0ac32392e351..5d8787f48edc 100644 --- a/src/agents/command/attempt-execution.ts +++ b/src/agents/command/attempt-execution.ts @@ -1,6 +1,6 @@ +import type { AcpRuntimeEvent } from "@openclaw/acp-core/runtime/types"; import { sanitizeForLog } from "../../../packages/terminal-core/src/ansi.js"; import { formatAcpErrorChain } from "../../acp/runtime/errors.js"; -import type { AcpRuntimeEvent } from "../../acp/runtime/types.js"; import { normalizeReplyPayload } from "../../auto-reply/reply/normalize-reply.js"; import type { ThinkLevel, VerboseLevel } from "../../auto-reply/thinking.js"; import { appendSessionTranscriptMessage } from "../../config/sessions/transcript-append.js"; diff --git a/src/agents/embedded-agent-runner/run/attempt.ts b/src/agents/embedded-agent-runner/run/attempt.ts index 911342536850..ff96b1e4bb25 100644 --- a/src/agents/embedded-agent-runner/run/attempt.ts +++ b/src/agents/embedded-agent-runner/run/attempt.ts @@ -1,5 +1,6 @@ import fs from "node:fs/promises"; import os from "node:os"; +import { MAX_IMAGE_BYTES } from "@openclaw/media-core/constants"; import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; import { isAcpRuntimeSpawnAvailable } from "../../../acp/runtime/availability.js"; import { buildHierarchyReinforcementMessage } from "../../../auto-reply/handoff-summarizer.js"; @@ -34,7 +35,6 @@ import { resolveHeartbeatSummaryForAgent } from "../../../infra/heartbeat-summar import { getMachineDisplayName } from "../../../infra/machine-name.js"; import { createCodexNativeWebSearchWrapper } from "../../../llm/providers/stream-wrappers/openai.js"; import type { AssistantMessage } from "../../../llm/types.js"; -import { MAX_IMAGE_BYTES } from "../../../media/constants.js"; import { listRegisteredPluginAgentPromptGuidance } from "../../../plugins/command-registry-state.js"; import { getCurrentPluginMetadataSnapshot } from "../../../plugins/current-plugin-metadata-snapshot.js"; import { buildAgentHookContextChannelFields } from "../../../plugins/hook-agent-context.js"; diff --git a/src/agents/generated-attachments.ts b/src/agents/generated-attachments.ts index fb8b7128414f..cc81a029deee 100644 --- a/src/agents/generated-attachments.ts +++ b/src/agents/generated-attachments.ts @@ -1,6 +1,6 @@ +import { basenameFromAnyPath } from "@openclaw/media-core/file-name"; import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; import { uniqueStrings } from "@openclaw/normalization-core/string-normalization"; -import { basenameFromAnyPath } from "../media/file-name.js"; export type AgentGeneratedAttachment = { type?: "image" | "audio" | "video" | "file"; diff --git a/src/agents/payload-redaction.ts b/src/agents/payload-redaction.ts index 0b0847c90893..45c696078b7c 100644 --- a/src/agents/payload-redaction.ts +++ b/src/agents/payload-redaction.ts @@ -1,6 +1,6 @@ import crypto from "node:crypto"; +import { estimateBase64DecodedBytes } from "@openclaw/media-core/base64"; import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce"; -import { estimateBase64DecodedBytes } from "../media/base64.js"; const REDACTED_IMAGE_DATA = ""; diff --git a/src/agents/provider-http-errors.ts b/src/agents/provider-http-errors.ts index 58dfa2fd8321..9d7075cd1ad4 100644 --- a/src/agents/provider-http-errors.ts +++ b/src/agents/provider-http-errors.ts @@ -1,7 +1,7 @@ export { asFiniteNumber } from "../../packages/normalization-core/src/number-coercion.js"; +import { readResponseWithLimit } from "@openclaw/media-core/read-response-with-limit"; import { normalizeOptionalString as trimToUndefined } from "../../packages/normalization-core/src/string-coerce.js"; import { redactSensitiveText } from "../logging/redact.js"; -import { readResponseWithLimit } from "../media/read-response-with-limit.js"; export { asBoolean } from "../utils/boolean.js"; export { normalizeOptionalString as trimToUndefined } from "../../packages/normalization-core/src/string-coerce.js"; diff --git a/src/agents/responses-image-payload-sanitizer.ts b/src/agents/responses-image-payload-sanitizer.ts index 0349a6228946..3a217e6df8e1 100644 --- a/src/agents/responses-image-payload-sanitizer.ts +++ b/src/agents/responses-image-payload-sanitizer.ts @@ -1,5 +1,5 @@ +import { sanitizeInlineImageDataUrl as sanitizeSharedInlineImageDataUrl } from "@openclaw/media-core/inline-image-data-url"; import { isRecord } from "@openclaw/normalization-core/record-coerce"; -import { sanitizeInlineImageDataUrl as sanitizeSharedInlineImageDataUrl } from "../media/inline-image-data-url.js"; const IMAGE_OMITTED_TEXT = "omitted image payload: invalid inline image data"; diff --git a/src/agents/sandbox-paths.ts b/src/agents/sandbox-paths.ts index 623564a9f963..ce3fa216d4de 100644 --- a/src/agents/sandbox-paths.ts +++ b/src/agents/sandbox-paths.ts @@ -1,6 +1,7 @@ import os from "node:os"; import path from "node:path"; import { URL } from "node:url"; +import { isPassThroughRemoteMediaSource } from "@openclaw/media-core/media-source-url"; import { isWindowsDrivePath } from "../infra/archive-path.js"; import { assertNoWindowsNetworkPath, @@ -10,7 +11,6 @@ import { import { assertNoPathAliasEscape, type PathAliasPolicy } from "../infra/path-alias-guards.js"; import { isPathInside } from "../infra/path-guards.js"; import { resolvePreferredOpenClawTmpDir } from "../infra/tmp-openclaw-dir.js"; -import { isPassThroughRemoteMediaSource } from "../media/media-source-url.js"; import { resolveConfigDir } from "../utils.js"; const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g; diff --git a/src/agents/tool-images.ts b/src/agents/tool-images.ts index 62ce9e980101..5e97a1ea55be 100644 --- a/src/agents/tool-images.ts +++ b/src/agents/tool-images.ts @@ -1,7 +1,7 @@ +import { canonicalizeBase64 } from "@openclaw/media-core/base64"; import { resolveIntegerOption } from "@openclaw/normalization-core/number-coercion"; import type { ImageContent } from "../llm/types.js"; import { createSubsystemLogger } from "../logging/subsystem.js"; -import { canonicalizeBase64 } from "../media/base64.js"; import { buildImageResizeSideGrid, getImageMetadata, diff --git a/src/agents/tools/common.ts b/src/agents/tools/common.ts index 1d3c77bfeb5c..2807904d4b55 100644 --- a/src/agents/tools/common.ts +++ b/src/agents/tools/common.ts @@ -1,3 +1,4 @@ +import { detectMime } from "@openclaw/media-core/mime"; import { asPositiveSafeInteger, asSafeIntegerInRange, @@ -6,7 +7,6 @@ import { import { normalizeStringEntries } from "@openclaw/normalization-core/string-normalization"; import type { TSchema } from "typebox"; import { readLocalFileSafely } from "../../infra/fs-safe.js"; -import { detectMime } from "../../media/mime.js"; import { readSnakeCaseParamRaw } from "../../param-key.js"; import type { ImageSanitizationLimits } from "../image-sanitization.js"; import type { diff --git a/src/agents/tools/image-tool.helpers.ts b/src/agents/tools/image-tool.helpers.ts index dd396a2a38d0..cf3a69381c3f 100644 --- a/src/agents/tools/image-tool.helpers.ts +++ b/src/agents/tools/image-tool.helpers.ts @@ -1,7 +1,7 @@ +import { estimateBase64DecodedBytes } from "@openclaw/media-core/base64"; import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import type { AssistantMessage } from "../../llm/types.js"; -import { estimateBase64DecodedBytes } from "../../media/base64.js"; import { extractAssistantText } from "../embedded-agent-utils.js"; import { isMinimaxVlmProvider } from "../minimax-vlm.js"; import { findNormalizedProviderValue, normalizeProviderId } from "../model-selection.js"; diff --git a/src/agents/tools/image-tool.test.ts b/src/agents/tools/image-tool.test.ts index 6e95ec2e464e..3caa76e25776 100644 --- a/src/agents/tools/image-tool.test.ts +++ b/src/agents/tools/image-tool.test.ts @@ -2,10 +2,10 @@ import fsSync from "node:fs"; import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; +import { isInboundPathAllowed } from "@openclaw/media-core/inbound-path-policy"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../../config/config.js"; import type { ModelDefinitionConfig } from "../../config/types.models.js"; -import { isInboundPathAllowed } from "../../media/inbound-path-policy.js"; import { encodePngRgba, fillPixel } from "../../media/png-encode.js"; import type { ImageDescriptionRequest, diff --git a/src/agents/tools/media-tool-shared.ts b/src/agents/tools/media-tool-shared.ts index 35a8fc34149e..042ba08dee0a 100644 --- a/src/agents/tools/media-tool-shared.ts +++ b/src/agents/tools/media-tool-shared.ts @@ -1,3 +1,4 @@ +import { normalizeInboundPathRoots } from "@openclaw/media-core/inbound-path-policy"; import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id"; import { normalizeOptionalLowercaseString, @@ -13,7 +14,6 @@ import type { OpenClawConfig } from "../../config/types.openclaw.js"; import type { SsrFPolicy } from "../../infra/net/ssrf.js"; import type { Model } from "../../llm/types.js"; import { resolveChannelInboundAttachmentRootsForChannel } from "../../media/channel-inbound-roots.js"; -import { normalizeInboundPathRoots } from "../../media/inbound-path-policy.js"; import { getDefaultLocalRoots } from "../../media/local-media-access.js"; import { readSnakeCaseParamRaw } from "../../param-key.js"; import { loadCapabilityManifestSnapshot } from "../../plugins/capability-provider-runtime.js"; diff --git a/src/agents/tools/nodes-tool-media.ts b/src/agents/tools/nodes-tool-media.ts index efba2d3f0dd6..f4dfa1fad00b 100644 --- a/src/agents/tools/nodes-tool-media.ts +++ b/src/agents/tools/nodes-tool-media.ts @@ -1,4 +1,5 @@ import crypto from "node:crypto"; +import { imageMimeFromFormat } from "@openclaw/media-core/mime"; import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce"; import { type CameraFacing, @@ -14,7 +15,6 @@ import { writeScreenRecordToFile, } from "../../cli/nodes-screen.js"; import { parseDurationMs } from "../../cli/parse-duration.js"; -import { imageMimeFromFormat } from "../../media/mime.js"; import type { ImageSanitizationLimits } from "../image-sanitization.js"; import type { AgentToolResult } from "../runtime/index.js"; import { sanitizeToolResultImages } from "../tool-images.js"; diff --git a/src/agents/tools/skill-workshop-tool.test.ts b/src/agents/tools/skill-workshop-tool.test.ts index 64bca28abfb1..cf4acb8c5c40 100644 --- a/src/agents/tools/skill-workshop-tool.test.ts +++ b/src/agents/tools/skill-workshop-tool.test.ts @@ -175,6 +175,9 @@ describe("skill_workshop tool", () => { status: "pending", query: "!!!", }); + expect((punctuationOnly.content[0] as { text: string }).text).toBe( + "No skill proposals matched.", + ); expect((punctuationOnly.details as { proposals: unknown[] }).proposals).toEqual([]); const inspected = await tool.execute("call-4", { diff --git a/src/agents/tools/video-generate-tool.test.ts b/src/agents/tools/video-generate-tool.test.ts index fda076e73e99..9555b36f60e5 100644 --- a/src/agents/tools/video-generate-tool.test.ts +++ b/src/agents/tools/video-generate-tool.test.ts @@ -1,6 +1,6 @@ +import { MAX_VIDEO_BYTES } from "@openclaw/media-core/constants"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../../config/config.js"; -import { MAX_VIDEO_BYTES } from "../../media/constants.js"; import * as mediaStore from "../../media/store.js"; import * as webMedia from "../../media/web-media.js"; import { diff --git a/src/auto-reply/reply/acp-projector.ts b/src/auto-reply/reply/acp-projector.ts index 44735084fe6e..5f16931f0a1b 100644 --- a/src/auto-reply/reply/acp-projector.ts +++ b/src/auto-reply/reply/acp-projector.ts @@ -1,8 +1,8 @@ +import type { AcpRuntimeEvent, AcpSessionUpdateTag } from "@openclaw/acp-core/runtime/types"; import { normalizeOptionalLowercaseString, normalizeOptionalString, } from "@openclaw/normalization-core/string-coerce"; -import type { AcpRuntimeEvent, AcpSessionUpdateTag } from "../../acp/runtime/types.js"; import { EmbeddedBlockChunker } from "../../agents/embedded-agent-block-chunker.js"; import { formatToolSummary, resolveToolDisplay } from "../../agents/tool-display.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; diff --git a/src/auto-reply/reply/acp-stream-settings.ts b/src/auto-reply/reply/acp-stream-settings.ts index 47c8030b928a..2eaea96b5428 100644 --- a/src/auto-reply/reply/acp-stream-settings.ts +++ b/src/auto-reply/reply/acp-stream-settings.ts @@ -1,4 +1,4 @@ -import type { AcpSessionUpdateTag } from "../../acp/runtime/types.js"; +import type { AcpSessionUpdateTag } from "@openclaw/acp-core/runtime/types"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { clampPositiveInteger, resolveEffectiveBlockStreamingConfig } from "./block-streaming.js"; diff --git a/src/auto-reply/reply/agent-runner-helpers.ts b/src/auto-reply/reply/agent-runner-helpers.ts index 2eac9a924094..ac3069c01536 100644 --- a/src/auto-reply/reply/agent-runner-helpers.ts +++ b/src/auto-reply/reply/agent-runner-helpers.ts @@ -1,9 +1,9 @@ +import { isAudioFileName } from "@openclaw/media-core/mime"; import { hasOutboundReplyContent, resolveSendableOutboundReplyParts, } from "openclaw/plugin-sdk/reply-payload"; import { loadSessionStore } from "../../config/sessions.js"; -import { isAudioFileName } from "../../media/mime.js"; import { normalizeVerboseLevel, type VerboseLevel } from "../thinking.js"; import type { ReplyPayload } from "../types.js"; import type { TypingSignaler } from "./typing-mode.js"; diff --git a/src/auto-reply/reply/commands-acp/shared.ts b/src/auto-reply/reply/commands-acp/shared.ts index bea2b3f74a6e..06ec17077890 100644 --- a/src/auto-reply/reply/commands-acp/shared.ts +++ b/src/auto-reply/reply/commands-acp/shared.ts @@ -1,11 +1,11 @@ import { randomUUID } from "node:crypto"; +import type { AcpRuntimeSessionMode } from "@openclaw/acp-core/runtime/types"; import { normalizeOptionalLowercaseString, normalizeOptionalString, } from "@openclaw/normalization-core/string-coerce"; import { toAcpRuntimeErrorText } from "../../../acp/runtime/error-text.js"; import type { AcpRuntimeError } from "../../../acp/runtime/errors.js"; -import type { AcpRuntimeSessionMode } from "../../../acp/runtime/types.js"; import { supportsAutomaticThreadBindingSpawn } from "../../../channels/thread-bindings-policy.js"; import type { AcpSessionRuntimeOptions } from "../../../config/sessions/types.js"; import { normalizeAgentId } from "../../../routing/session-key.js"; diff --git a/src/auto-reply/reply/current-turn-images.ts b/src/auto-reply/reply/current-turn-images.ts index 323185eba749..8d6d361418b9 100644 --- a/src/auto-reply/reply/current-turn-images.ts +++ b/src/auto-reply/reply/current-turn-images.ts @@ -1,9 +1,9 @@ +import { mimeTypeFromFilePath } from "@openclaw/media-core/mime"; import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { logVerbose } from "../../globals.js"; import { formatErrorMessage } from "../../infra/errors.js"; import type { ImageContent } from "../../llm/types.js"; -import { mimeTypeFromFilePath } from "../../media/mime.js"; import type { PromptImageOrderEntry } from "../../media/prompt-image-order.js"; import type { MsgContext } from "../templating.js"; import { resolveAgentTurnAttachments } from "./agent-turn-attachments.js"; diff --git a/src/auto-reply/reply/history-media.ts b/src/auto-reply/reply/history-media.ts index f38ea5db5c60..7fd79031bb5b 100644 --- a/src/auto-reply/reply/history-media.ts +++ b/src/auto-reply/reply/history-media.ts @@ -1,6 +1,6 @@ +import { mimeTypeFromFilePath } from "@openclaw/media-core/mime"; import { asFiniteNumber } from "@openclaw/normalization-core/number-coercion"; import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; -import { mimeTypeFromFilePath } from "../../media/mime.js"; import type { MsgContext } from "../templating.js"; import type { HistoryEntry, HistoryMediaEntry } from "./history.types.js"; diff --git a/src/auto-reply/reply/reply-media-paths.ts b/src/auto-reply/reply/reply-media-paths.ts index 732cc45c1d22..4799ac952274 100644 --- a/src/auto-reply/reply/reply-media-paths.ts +++ b/src/auto-reply/reply/reply-media-paths.ts @@ -1,4 +1,5 @@ import path from "node:path"; +import { isPassThroughRemoteMediaSource } from "@openclaw/media-core/media-source-url"; import { resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload"; import { resolveSessionAgentId } from "../../agents/agent-scope.js"; import { resolvePathFromInput, toRelativeWorkspacePath } from "../../agents/path-policy.js"; @@ -11,7 +12,6 @@ import { ensureSandboxWorkspaceForSession } from "../../agents/sandbox.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { logVerbose } from "../../globals.js"; import { resolveChannelAccountMediaMaxMb } from "../../media/configured-max-bytes.js"; -import { isPassThroughRemoteMediaSource } from "../../media/media-source-url.js"; import { resolveOutboundAttachmentFromUrl } from "../../media/outbound-attachment.js"; import { resolveAgentScopedOutboundMediaAccess } from "../../media/read-capability.js"; import { MEDIA_MAX_BYTES } from "../../media/store.js"; diff --git a/src/auto-reply/reply/stage-sandbox-media.ts b/src/auto-reply/reply/stage-sandbox-media.ts index cb9f3ffc4c0e..125cc072f87a 100644 --- a/src/auto-reply/reply/stage-sandbox-media.ts +++ b/src/auto-reply/reply/stage-sandbox-media.ts @@ -2,6 +2,7 @@ import { spawn } from "node:child_process"; import fs from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; +import { isInboundPathAllowed } from "@openclaw/media-core/inbound-path-policy"; import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; import { assertSandboxPath } from "../../agents/sandbox-paths.js"; import { ensureSandboxWorkspaceForSession } from "../../agents/sandbox.js"; @@ -12,7 +13,6 @@ import { root as fsRoot, FsSafeError } from "../../infra/fs-safe.js"; import { normalizeScpRemoteHost, normalizeScpRemotePath } from "../../infra/scp-host.js"; import { resolvePreferredOpenClawTmpDir } from "../../infra/tmp-openclaw-dir.js"; import { resolveChannelRemoteInboundAttachmentRoots } from "../../media/channel-inbound-roots.js"; -import { isInboundPathAllowed } from "../../media/inbound-path-policy.js"; import { resolveInboundMediaReference } from "../../media/media-reference.js"; import { getMediaDir, MEDIA_MAX_BYTES } from "../../media/store.js"; import { CONFIG_DIR } from "../../utils.js"; diff --git a/src/channels/draft-stream-loop.test.ts b/src/channels/draft-stream-loop.test.ts index dc6a811ffcc7..b6c95bb0ab24 100644 --- a/src/channels/draft-stream-loop.test.ts +++ b/src/channels/draft-stream-loop.test.ts @@ -1,4 +1,4 @@ -import { setImmediate as realSetImmediate } from "node:timers"; +import { setImmediate as nextMacrotask } from "node:timers/promises"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { MAX_TIMER_TIMEOUT_MS } from "../shared/number-coercion.js"; import { createDraftStreamLoop } from "./draft-stream-loop.js"; @@ -9,7 +9,7 @@ const flushMicrotasks = async () => { }; const flushMacrotask = async () => { - await new Promise((resolve) => realSetImmediate(resolve)); + await nextMacrotask(); }; async function waitForBackgroundFlushError( diff --git a/src/channels/mention-pattern-policy.ts b/src/channels/mention-pattern-policy.ts index 467af41be5dd..84d364025e8b 100644 --- a/src/channels/mention-pattern-policy.ts +++ b/src/channels/mention-pattern-policy.ts @@ -32,6 +32,10 @@ function isMentionPatternsPolicyConfig(value: unknown): value is MentionPatterns return value != null && typeof value === "object" && !Array.isArray(value); } +function isRecord(value: unknown): value is Record { + return value != null && typeof value === "object" && !Array.isArray(value); +} + function resolveProviderMentionPatternsPolicy( cfg: OpenClawConfig | undefined, provider: string | undefined, @@ -39,7 +43,8 @@ function resolveProviderMentionPatternsPolicy( if (!cfg || !provider) { return undefined; } - const policy = cfg.channels?.[provider]?.mentionPatterns; + const channelConfig = cfg.channels?.[provider]; + const policy = isRecord(channelConfig) ? channelConfig.mentionPatterns : undefined; return isMentionPatternsPolicyConfig(policy) ? policy : undefined; } diff --git a/src/channels/plugins/config-write-policy-shared.ts b/src/channels/plugins/config-write-policy-shared.ts index 17f2ebc6c645..2fdd117e419b 100644 --- a/src/channels/plugins/config-write-policy-shared.ts +++ b/src/channels/plugins/config-write-policy-shared.ts @@ -11,7 +11,7 @@ type ChannelConfigWithAccounts = { }; type ConfigWritePolicyConfig = { - channels?: Record; + channels?: Record; }; export type ConfigWriteScopeLike = { @@ -55,7 +55,10 @@ function resolveChannelConfig( if (!channelId) { return undefined; } - return cfg.channels?.[channelId]; + const channelConfig = cfg.channels?.[channelId]; + return channelConfig != null && typeof channelConfig === "object" && !Array.isArray(channelConfig) + ? (channelConfig as ChannelConfigWithAccounts) + : undefined; } function resolveChannelAccountConfig( diff --git a/src/channels/plugins/outbound/direct-text-media.ts b/src/channels/plugins/outbound/direct-text-media.ts index 5b0a718886e0..a2b3851a2123 100644 --- a/src/channels/plugins/outbound/direct-text-media.ts +++ b/src/channels/plugins/outbound/direct-text-media.ts @@ -25,6 +25,18 @@ type DirectSendFn, TResult extends DirectS text: string, opts: TOpts, ) => Promise; + +function asRecord(value: unknown): Record | undefined { + return value != null && typeof value === "object" && !Array.isArray(value) + ? (value as Record) + : undefined; +} + +function readNumberField(record: Record | undefined, key: string) { + const value = record?.[key]; + return typeof value === "number" ? value : undefined; +} + export { resolvePayloadMediaUrls, sendPayloadMediaSequence, @@ -50,9 +62,14 @@ export function createScopedChannelMediaMaxBytesResolver(channel: string) { resolveScopedChannelMediaMaxBytes({ cfg: params.cfg, accountId: params.accountId, - resolveChannelLimitMb: ({ cfg, accountId }) => - (cfg.channels?.[channel]?.accounts?.[accountId] as { mediaMaxMb?: number } | undefined) - ?.mediaMaxMb ?? cfg.channels?.[channel]?.mediaMaxMb, + resolveChannelLimitMb: ({ cfg, accountId }) => { + const channelConfig = asRecord(cfg.channels?.[channel]); + const accountConfig = asRecord(asRecord(channelConfig?.accounts)?.[accountId]); + return ( + readNumberField(accountConfig, "mediaMaxMb") ?? + readNumberField(channelConfig, "mediaMaxMb") + ); + }, }); } diff --git a/src/channels/plugins/read-only.ts b/src/channels/plugins/read-only.ts index 9f539a76cb5c..398db19aa439 100644 --- a/src/channels/plugins/read-only.ts +++ b/src/channels/plugins/read-only.ts @@ -299,7 +299,7 @@ function rebindChannelConfig( ...cfg, channels: { ...cfg.channels, - [sourceChannelId]: (cfg.channels as Record)[targetChannelId], + [sourceChannelId]: cfg.channels[targetChannelId], }, }; } diff --git a/src/channels/plugins/setup-wizard-helpers.ts b/src/channels/plugins/setup-wizard-helpers.ts index 4ce5de6be539..e496bb9b0fbf 100644 --- a/src/channels/plugins/setup-wizard-helpers.ts +++ b/src/channels/plugins/setup-wizard-helpers.ts @@ -32,6 +32,20 @@ function loadProviderAuthInput() { return providerAuthInputPromise; } +function asRecord(value: unknown): Record | undefined { + return value != null && typeof value === "object" && !Array.isArray(value) + ? (value as Record) + : undefined; +} + +function asAllowFromList(value: unknown): ReadonlyArray | undefined { + return Array.isArray(value) + ? value.filter( + (entry): entry is string | number => typeof entry === "string" || typeof entry === "number", + ) + : undefined; +} + export const promptAccountId: PromptAccountId = async (params: PromptAccountIdParams) => { const existingIds = params.listAccountIds(params.cfg); const initial = params.currentId?.trim() || params.defaultAccountId || DEFAULT_ACCOUNT_ID; @@ -537,14 +551,17 @@ export function setChannelDmPolicyWithAllowFrom(params: { dmPolicy: DmPolicy; }): OpenClawConfig { const { cfg, channel, dmPolicy } = params; + const channelConfig = asRecord(cfg.channels?.[channel]); const allowFrom = - dmPolicy === "open" ? addWildcardAllowFrom(cfg.channels?.[channel]?.allowFrom) : undefined; + dmPolicy === "open" + ? addWildcardAllowFrom(asAllowFromList(channelConfig?.allowFrom)) + : undefined; return { ...cfg, channels: { ...cfg.channels, [channel]: { - ...cfg.channels?.[channel], + ...channelConfig, dmPolicy, ...(allowFrom ? { allowFrom } : {}), }, diff --git a/src/channels/typing.test.ts b/src/channels/typing.test.ts index 0c3e2f248b56..f68d4819322d 100644 --- a/src/channels/typing.test.ts +++ b/src/channels/typing.test.ts @@ -80,14 +80,6 @@ describe("createTypingCallbacks", () => { vi.useRealTimers(); }); - afterEach(() => { - if (vi.isFakeTimers()) { - vi.clearAllTimers(); - } - vi.useRealTimers(); - vi.restoreAllMocks(); - }); - it("invokes start on reply start", async () => { const { start, onStartError, callbacks } = createTypingHarness(); diff --git a/src/cli/capability-cli.ts b/src/cli/capability-cli.ts index e5f3b41d2e3b..edf031c4b9dd 100644 --- a/src/cli/capability-cli.ts +++ b/src/cli/capability-cli.ts @@ -4,6 +4,7 @@ import fs from "node:fs/promises"; import path from "node:path"; import { Readable } from "node:stream"; import { pipeline } from "node:stream/promises"; +import { detectMime, extensionForMime, normalizeMimeType } from "@openclaw/media-core/mime"; import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, @@ -61,7 +62,6 @@ import { transcribeAudioFile, } from "../media-understanding/runtime.js"; import { convertHeicToJpeg, getImageMetadata } from "../media/media-services.js"; -import { detectMime, extensionForMime, normalizeMimeType } from "../media/mime.js"; import { saveMediaBuffer } from "../media/store.js"; import { createEmbeddingProvider, diff --git a/src/config/types.acp.ts b/src/config/types.acp.ts index da2791d7006a..4a46db7a42e6 100644 --- a/src/config/types.acp.ts +++ b/src/config/types.acp.ts @@ -1,4 +1,4 @@ -import type { AcpSessionUpdateTag } from "../acp/runtime/types.js"; +import type { AcpSessionUpdateTag } from "@openclaw/acp-core/runtime/types"; export type AcpDispatchConfig = { /** Master switch for ACP turn dispatch in the reply pipeline. */ diff --git a/src/config/types.channels.ts b/src/config/types.channels.ts index 25ed9bcdc6f3..85fe3f560e9c 100644 --- a/src/config/types.channels.ts +++ b/src/config/types.channels.ts @@ -38,6 +38,16 @@ export type ExtensionNestedPolicyConfig = { [key: string]: unknown; }; +export type ExtensionAccountConfig = ExtensionNestedPolicyConfig & { + defaultTo?: string | number; + dmPolicy?: string; + dm?: ExtensionNestedPolicyConfig; + mediaMaxMb?: number; + configWrites?: boolean; +}; + +type OpenWorldChannelConfig = ReturnType; + /** * Base type for extension channel config sections. * Extensions can use this as a starting point for their channel config. @@ -51,7 +61,7 @@ export type ExtensionChannelConfig = { defaultAccount?: string; dmPolicy?: string; groupPolicy?: GroupPolicy; - mentionPatterns?: MentionPatternsPolicyConfig; + mentionPatterns?: MentionPatternsPolicyConfig | string[]; contextVisibility?: ContextVisibilityMode; healthMonitor?: ChannelHealthMonitorConfig; dm?: ExtensionNestedPolicyConfig; @@ -74,7 +84,7 @@ export type ExtensionChannelConfig = { botLoopProtection?: ChannelBotLoopProtectionConfig; spawnSubagentSessions?: boolean; dangerouslyAllowPrivateNetwork?: boolean; - accounts?: Record; + accounts?: Record; [key: string]: unknown; }; @@ -93,10 +103,7 @@ export interface ChannelsConfig { whatsapp?: WhatsAppConfig; /** * Channel sections are plugin-owned and keyed by arbitrary channel ids. - * Keep the lookup permissive so augmented channel configs remain ergonomic at call sites. + * Open-world config keeps SDK/plugin-owned sections ergonomic for dynamic ids. */ - // Plugin-owned channel sections are open-world config; narrowing this breaks - // SDK config-write helpers that accept account-shaped channel records. - // oxlint-disable-next-line typescript/no-explicit-any - [key: string]: any; + [key: string]: OpenWorldChannelConfig; } diff --git a/src/config/zod-schema.providers-core.ts b/src/config/zod-schema.providers-core.ts index 0e30dd844f25..38d2b5389ff8 100644 --- a/src/config/zod-schema.providers-core.ts +++ b/src/config/zod-schema.providers-core.ts @@ -1,7 +1,7 @@ +import { isValidInboundPathRootPattern } from "@openclaw/media-core/inbound-path-policy"; import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; import { z } from "zod"; import { isSafeScpRemoteHost } from "../infra/scp-host.js"; -import { isValidInboundPathRootPattern } from "../media/inbound-path-policy.js"; import { normalizeCommandDescription, normalizeSlashCommandName, diff --git a/src/cron/isolated-agent/delivery-dispatch.ts b/src/cron/isolated-agent/delivery-dispatch.ts index 9c287c495e7a..590d67e29f97 100644 --- a/src/cron/isolated-agent/delivery-dispatch.ts +++ b/src/cron/isolated-agent/delivery-dispatch.ts @@ -1,3 +1,4 @@ +import { isAudioFileName } from "@openclaw/media-core/mime"; import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; import { retireSessionMcpRuntime } from "../../agents/agent-bundle-mcp-tools.js"; import type { ReplyPayload } from "../../auto-reply/reply-payload.js"; @@ -31,7 +32,6 @@ import { import type { SourceDeliveryOutcome } from "../../infra/outbound/source-delivery-plan.js"; import { normalizeTargetForProvider } from "../../infra/outbound/target-normalization.js"; import { hasReplyPayloadContent } from "../../interactive/payload.js"; -import { isAudioFileName } from "../../media/mime.js"; import { stringifyRouteThreadId } from "../../plugin-sdk/channel-route.js"; import { isCronSessionKey, diff --git a/src/gateway/chat-attachments.test.ts b/src/gateway/chat-attachments.test.ts index 47485efebbec..8dadbce29679 100644 --- a/src/gateway/chat-attachments.test.ts +++ b/src/gateway/chat-attachments.test.ts @@ -21,8 +21,8 @@ vi.mock("../media/store.js", async (importOriginal) => { }; }); +import { MAX_IMAGE_BYTES } from "@openclaw/media-core/constants"; import type { OpenClawConfig } from "../config/types.openclaw.js"; -import { MAX_IMAGE_BYTES } from "../media/constants.js"; import { buildMessageWithAttachments, type ChatAttachment, diff --git a/src/gateway/chat-attachments.ts b/src/gateway/chat-attachments.ts index 6344566926da..9608111e1f99 100644 --- a/src/gateway/chat-attachments.ts +++ b/src/gateway/chat-attachments.ts @@ -1,9 +1,9 @@ +import { estimateBase64DecodedBytes } from "@openclaw/media-core/base64"; +import { MAX_IMAGE_BYTES } from "@openclaw/media-core/constants"; +import { extensionForMime, mimeTypeFromFilePath } from "@openclaw/media-core/mime"; import { normalizeOptionalLowercaseString } from "@openclaw/normalization-core/string-coerce"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { formatErrorMessage } from "../infra/errors.js"; -import { estimateBase64DecodedBytes } from "../media/base64.js"; -import { MAX_IMAGE_BYTES } from "../media/constants.js"; -import { extensionForMime, mimeTypeFromFilePath } from "../media/mime.js"; import type { PromptImageOrderEntry } from "../media/prompt-image-order.js"; import { sniffMimeFromBase64 } from "../media/sniff-mime-from-base64.js"; import { deleteMediaBuffer, saveMediaBuffer } from "../media/store.js"; diff --git a/src/gateway/control-ui.ts b/src/gateway/control-ui.ts index 7be2f265e7b6..e5211e9ee5c6 100644 --- a/src/gateway/control-ui.ts +++ b/src/gateway/control-ui.ts @@ -2,6 +2,7 @@ import { createHmac, randomBytes, timingSafeEqual } from "node:crypto"; import fs from "node:fs"; import type { IncomingMessage, ServerResponse } from "node:http"; import path from "node:path"; +import { detectMime } from "@openclaw/media-core/mime"; import { asDateTimestampMs, resolveTimestampMsToIsoString, @@ -21,7 +22,6 @@ import { isWithinDir } from "../infra/path-safety.js"; import { assertLocalMediaAllowed, getDefaultLocalRoots } from "../media/local-media-access.js"; import { getAgentScopedMediaLocalRoots } from "../media/local-roots.js"; import { resolveMediaReferenceLocalPath } from "../media/media-reference.js"; -import { detectMime } from "../media/mime.js"; import { AVATAR_MAX_BYTES } from "../shared/avatar-policy.js"; import { resolveUserPath } from "../utils.js"; import { resolveRuntimeServiceVersion } from "../version.js"; diff --git a/src/gateway/managed-image-attachments.ts b/src/gateway/managed-image-attachments.ts index 4bd2d214d709..a37fbaa2aab8 100644 --- a/src/gateway/managed-image-attachments.ts +++ b/src/gateway/managed-image-attachments.ts @@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto"; import fs from "node:fs/promises"; import type { IncomingMessage, ServerResponse } from "node:http"; import path from "node:path"; +import { isPassThroughRemoteMediaSource } from "@openclaw/media-core/media-source-url"; import { resolveDefaultAgentId } from "../agents/agent-scope-config.js"; import { getRuntimeConfig } from "../config/config.js"; import { resolveStateDir } from "../config/paths.js"; @@ -14,7 +15,6 @@ import { getImageMetadata, readImageProbeFromHeader, } from "../media/media-services.js"; -import { isPassThroughRemoteMediaSource } from "../media/media-source-url.js"; import { MEDIA_MAX_BYTES, saveMediaBuffer, saveMediaSource } from "../media/store.js"; import { resolveUserPath } from "../utils.js"; import type { AuthRateLimiter } from "./auth-rate-limit.js"; diff --git a/src/gateway/openai-http.ts b/src/gateway/openai-http.ts index 12a06bd22425..783f979069ba 100644 --- a/src/gateway/openai-http.ts +++ b/src/gateway/openai-http.ts @@ -1,5 +1,6 @@ import { randomUUID } from "node:crypto"; import type { IncomingMessage, ServerResponse } from "node:http"; +import { estimateBase64DecodedBytes } from "@openclaw/media-core/base64"; import { resolveIntegerOption } from "@openclaw/normalization-core/number-coercion"; import { normalizeLowercaseStringOrEmpty, @@ -21,7 +22,6 @@ import { agentCommandFromIngress } from "../commands/agent.js"; import type { GatewayHttpChatCompletionsConfig } from "../config/types.gateway.js"; import { emitAgentEvent, onAgentEvent } from "../infra/agent-events.js"; import { logWarn } from "../logger.js"; -import { estimateBase64DecodedBytes } from "../media/base64.js"; import { DEFAULT_INPUT_IMAGE_MAX_BYTES, DEFAULT_INPUT_IMAGE_MIMES, diff --git a/src/gateway/server-methods/chat-reply-media.ts b/src/gateway/server-methods/chat-reply-media.ts index 86b3d1b578c0..af6302e5d4e9 100644 --- a/src/gateway/server-methods/chat-reply-media.ts +++ b/src/gateway/server-methods/chat-reply-media.ts @@ -1,9 +1,9 @@ +import { isPassThroughRemoteMediaSource } from "@openclaw/media-core/media-source-url"; +import { isAudioFileName } from "@openclaw/media-core/mime"; import { resolveAgentWorkspaceDir } from "../../agents/agent-scope.js"; import type { ReplyPayload } from "../../auto-reply/reply-payload.js"; import { createReplyMediaPathNormalizer } from "../../auto-reply/reply/reply-media-paths.runtime.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; -import { isPassThroughRemoteMediaSource } from "../../media/media-source-url.js"; -import { isAudioFileName } from "../../media/mime.js"; import { resolveSendableOutboundReplyParts } from "../../plugin-sdk/reply-payload.js"; function isDataUrlMedia(mediaUrl: string): boolean { diff --git a/src/gateway/server-methods/chat-webchat-media.ts b/src/gateway/server-methods/chat-webchat-media.ts index ca98cbd8bef3..bc2d9bc7a771 100644 --- a/src/gateway/server-methods/chat-webchat-media.ts +++ b/src/gateway/server-methods/chat-webchat-media.ts @@ -1,11 +1,11 @@ import path from "node:path"; +import { estimateBase64DecodedBytes } from "@openclaw/media-core/base64"; +import { isAudioFileName } from "@openclaw/media-core/mime"; import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce"; import type { ReplyPayload } from "../../auto-reply/reply-payload.js"; import { openLocalFileSafely } from "../../infra/fs-safe.js"; import { assertNoWindowsNetworkPath, safeFileURLToPath } from "../../infra/local-file-access.js"; -import { estimateBase64DecodedBytes } from "../../media/base64.js"; import { assertLocalMediaAllowed, LocalMediaAccessError } from "../../media/local-media-access.js"; -import { isAudioFileName } from "../../media/mime.js"; import { resolveSendableOutboundReplyParts } from "../../plugin-sdk/reply-payload.js"; import { sanitizeReplyDirectiveId } from "../../utils/directive-tags.js"; import { isSuppressedControlReplyText } from "../control-reply-text.js"; diff --git a/src/gateway/server-methods/chat.ts b/src/gateway/server-methods/chat.ts index f062aa5ddb75..fc513551a0a1 100644 --- a/src/gateway/server-methods/chat.ts +++ b/src/gateway/server-methods/chat.ts @@ -1,6 +1,7 @@ import { createHash } from "node:crypto"; import fs from "node:fs"; import path from "node:path"; +import { isAudioFileName } from "@openclaw/media-core/mime"; import { asOptionalRecord } from "@openclaw/normalization-core/record-coerce"; import { uniqueStrings } from "@openclaw/normalization-core/string-normalization"; import { @@ -61,7 +62,6 @@ import { appendLocalMediaParentRoots, getAgentScopedMediaLocalRoots, } from "../../media/local-roots.js"; -import { isAudioFileName } from "../../media/mime.js"; import type { PromptImageOrderEntry } from "../../media/prompt-image-order.js"; import { deleteMediaBuffer, diff --git a/src/image-generation/image-assets.ts b/src/image-generation/image-assets.ts index da7918cde4ac..19c7437521bb 100644 --- a/src/image-generation/image-assets.ts +++ b/src/image-generation/image-assets.ts @@ -1,9 +1,9 @@ +import { canonicalizeBase64 } from "@openclaw/media-core/base64"; import { isRecord } from "@openclaw/normalization-core/record-coerce"; import { normalizeOptionalLowercaseString, normalizeOptionalString, } from "@openclaw/normalization-core/string-coerce"; -import { canonicalizeBase64 } from "../media/base64.js"; import type { GeneratedImageAsset, ImageGenerationSourceImage } from "./types.js"; const DEFAULT_IMAGE_MIME_TYPE = "image/png"; diff --git a/src/infra/clawhub.ts b/src/infra/clawhub.ts index 28375601f755..9d88577fe62a 100644 --- a/src/infra/clawhub.ts +++ b/src/infra/clawhub.ts @@ -2,12 +2,12 @@ import { createHash } from "node:crypto"; import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; +import { readResponseWithLimit } from "@openclaw/media-core/read-response-with-limit"; import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, } from "@openclaw/normalization-core/string-coerce"; import { normalizeStringEntries } from "@openclaw/normalization-core/string-normalization"; -import { readResponseWithLimit } from "../media/read-response-with-limit.js"; import { parseStrictPositiveInteger } from "./parse-finite-number.js"; import { isAtLeast, parseSemver } from "./runtime-guard.js"; import { compareComparableSemver, parseComparableSemver } from "./semver-compare.js"; diff --git a/src/infra/outbound/message-action-params.ts b/src/infra/outbound/message-action-params.ts index 057abcc1ad64..c2c29f308816 100644 --- a/src/infra/outbound/message-action-params.ts +++ b/src/infra/outbound/message-action-params.ts @@ -1,3 +1,5 @@ +import { basenameFromAnyPath } from "@openclaw/media-core/file-name"; +import { extensionForMime } from "@openclaw/media-core/mime"; import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; import { assertMediaNotDataUrl, resolveSandboxedMediaSource } from "../../agents/sandbox-paths.js"; import { readStringParam } from "../../agents/tools/common.js"; @@ -7,7 +9,6 @@ import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { root } from "../../infra/fs-safe.js"; import { basenameFromMediaSource } from "../../infra/local-file-access.js"; import { resolveChannelAccountMediaMaxMb } from "../../media/configured-max-bytes.js"; -import { basenameFromAnyPath } from "../../media/file-name.js"; import { buildOutboundMediaLoadOptions, resolveOutboundMediaAccess, @@ -15,7 +16,6 @@ import { type OutboundMediaAccess, type OutboundMediaReadFile, } from "../../media/load-options.js"; -import { extensionForMime } from "../../media/mime.js"; import { loadWebMedia } from "../../media/web-media.js"; import { resolveSnakeCaseParamKey } from "../../param-key.js"; import { readBooleanParam as readBooleanParamShared } from "../../plugin-sdk/boolean-param.js"; diff --git a/src/infra/outbound/message-action-runner.test-helpers.ts b/src/infra/outbound/message-action-runner.test-helpers.ts index 10eaf3f0508a..f47033602cdf 100644 --- a/src/infra/outbound/message-action-runner.test-helpers.ts +++ b/src/infra/outbound/message-action-runner.test-helpers.ts @@ -28,6 +28,14 @@ export const directChatConfig = { export const directOutbound: ChannelOutboundAdapter = { deliveryMode: "direct" }; +function hasChannelBotToken(channelConfig: unknown): boolean { + if (channelConfig == null || typeof channelConfig !== "object" || Array.isArray(channelConfig)) { + return false; + } + const token = (channelConfig as Record).botToken; + return typeof token === "string" && Boolean(token.trim()); +} + export const runDryAction = (params: { cfg: OpenClawConfig; action: ChannelMessageActionName; @@ -119,7 +127,7 @@ function createConfiguredTestPlugin(params: { export const workspaceTestPlugin = createConfiguredTestPlugin({ id: "workspace", - isConfigured: (cfg) => Boolean(cfg.channels?.workspace?.botToken?.trim()), + isConfigured: (cfg) => hasChannelBotToken(cfg.channels?.workspace), normalizeTarget: (raw) => normalizeWorkspaceTarget(raw) || undefined, resolveTarget: (input) => { const normalized = normalizeWorkspaceTarget(input); @@ -136,7 +144,7 @@ export const workspaceTestPlugin = createConfiguredTestPlugin({ export const forumTestPlugin = createConfiguredTestPlugin({ id: "forum", - isConfigured: (cfg) => Boolean(cfg.channels?.forum?.botToken?.trim()), + isConfigured: (cfg) => hasChannelBotToken(cfg.channels?.forum), normalizeTarget: (raw) => raw.trim() || undefined, resolveTarget: (input) => { const normalized = input.trim(); diff --git a/src/infra/watch-node.test.ts b/src/infra/watch-node.test.ts index 5c3299d85728..a1cfea591c9c 100644 --- a/src/infra/watch-node.test.ts +++ b/src/infra/watch-node.test.ts @@ -133,7 +133,9 @@ describe("watch-node script", () => { expect(watchPaths).toContain("packages/gateway-client/src"); expect(watchPaths).toContain("packages/gateway-protocol/src"); expect(watchPaths).toContain("packages/markdown-core/src"); + expect(watchPaths).toContain("packages/media-core/src"); expect(watchPaths).toContain("packages/media-generation-core/src"); + expect(watchPaths).toContain("packages/acp-core/src"); expect(watchPaths).toContain("packages/net-policy/src"); expect(watchPaths).toContain("tsdown.config.ts"); expect(watchOptions.ignoreInitial).toBe(true); @@ -144,10 +146,13 @@ describe("watch-node script", () => { expect(watchOptions.ignored("packages/gateway-protocol/src/schema/cron.ts")).toBe(false); expect(watchOptions.ignored("packages/markdown-core/src/ir.ts")).toBe(false); expect(watchOptions.ignored("packages/markdown-core/src/ir.test.ts")).toBe(true); + expect(watchOptions.ignored("packages/media-core/src/mime.ts")).toBe(false); + expect(watchOptions.ignored("packages/media-core/src/mime.test.ts")).toBe(true); expect(watchOptions.ignored("packages/media-generation-core/src/model-ref.ts")).toBe(false); expect(watchOptions.ignored("packages/media-generation-core/src/model-ref.test.ts")).toBe( true, ); + expect(watchOptions.ignored("packages/acp-core/src/runtime/types.ts")).toBe(false); expect(watchOptions.ignored("packages/net-policy/src/ip.ts")).toBe(false); expect(watchOptions.ignored("packages/net-policy/src/ip.test.ts")).toBe(true); expect(watchOptions.ignored("extensions")).toBe(false); diff --git a/src/link-understanding/runner.ts b/src/link-understanding/runner.ts index 57057c648840..8d7811ce7db7 100644 --- a/src/link-understanding/runner.ts +++ b/src/link-understanding/runner.ts @@ -1,3 +1,4 @@ +import { readResponseWithLimit } from "@openclaw/media-core/read-response-with-limit"; import type { MsgContext } from "../auto-reply/templating.js"; import { applyTemplate } from "../auto-reply/templating.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; @@ -10,7 +11,6 @@ import { normalizeMediaUnderstandingChatType, resolveMediaUnderstandingScope, } from "../media-understanding/scope.js"; -import { readResponseWithLimit } from "../media/read-response-with-limit.js"; import { runCommandWithTimeout } from "../process/exec.js"; import { DEFAULT_LINK_TIMEOUT_SECONDS } from "./defaults.js"; import { extractLinksFromMessage } from "./detect.js"; diff --git a/src/media-understanding/attachments.cache.ts b/src/media-understanding/attachments.cache.ts index 6f6ac570701c..74b668e38d46 100644 --- a/src/media-understanding/attachments.cache.ts +++ b/src/media-understanding/attachments.cache.ts @@ -1,5 +1,10 @@ import fs from "node:fs/promises"; import path from "node:path"; +import { + isInboundPathAllowed, + mergeInboundPathRoots, +} from "@openclaw/media-core/inbound-path-policy"; +import { detectMime } from "@openclaw/media-core/mime"; import { logVerbose, shouldLogVerbose } from "../globals.js"; import { FsSafeError, openLocalFileSafely } from "../infra/fs-safe.js"; import type { SsrFPolicy } from "../infra/net/ssrf.js"; @@ -9,9 +14,7 @@ import { type MediaFetchRetryOptions, MediaFetchError, } from "../media/fetch.js"; -import { isInboundPathAllowed, mergeInboundPathRoots } from "../media/inbound-path-policy.js"; import { getDefaultMediaLocalRoots } from "../media/local-roots.js"; -import { detectMime } from "../media/mime.js"; import { buildRandomTempFilePath } from "../plugin-sdk/temp-path.js"; import { normalizeAttachmentPath } from "./attachments.normalize.js"; import { MediaUnderstandingSkipError } from "./errors.js"; diff --git a/src/media-understanding/attachments.normalize.ts b/src/media-understanding/attachments.normalize.ts index 27facfb4b4cb..5622197ada7f 100644 --- a/src/media-understanding/attachments.normalize.ts +++ b/src/media-understanding/attachments.normalize.ts @@ -1,7 +1,7 @@ +import { getFileExtension, isAudioFileName, kindFromMime } from "@openclaw/media-core/mime"; import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; import type { MsgContext } from "../auto-reply/templating.js"; import { assertNoWindowsNetworkPath, safeFileURLToPath } from "../infra/local-file-access.js"; -import { getFileExtension, isAudioFileName, kindFromMime } from "../media/mime.js"; import type { MediaAttachment } from "./types.js"; export function normalizeAttachmentPath(raw?: string | null): string | undefined { diff --git a/src/media-understanding/runner.ts b/src/media-understanding/runner.ts index a6afd4c882ac..a009a1046270 100644 --- a/src/media-understanding/runner.ts +++ b/src/media-understanding/runner.ts @@ -2,6 +2,7 @@ import { constants as fsConstants } from "node:fs"; import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; +import { mergeInboundPathRoots } from "@openclaw/media-core/inbound-path-policy"; import { findNormalizedProviderValue } from "@openclaw/model-catalog-core/provider-id"; import { normalizeLowercaseStringOrEmpty, @@ -33,7 +34,6 @@ import { logVerbose, shouldLogVerbose } from "../globals.js"; import { resolvePreferredOpenClawTmpDir } from "../infra/tmp-openclaw-dir.js"; import { logWarn } from "../logger.js"; import { resolveChannelInboundAttachmentRoots } from "../media/channel-inbound-roots.js"; -import { mergeInboundPathRoots } from "../media/inbound-path-policy.js"; import { getDefaultMediaLocalRoots } from "../media/local-roots.js"; import { runExec } from "../process/exec.js"; import type { ActiveMediaModel } from "./active-model.types.js"; diff --git a/src/media-understanding/runtime.ts b/src/media-understanding/runtime.ts index dc3bc662d67b..2730f7276254 100644 --- a/src/media-understanding/runtime.ts +++ b/src/media-understanding/runtime.ts @@ -1,7 +1,7 @@ import path from "node:path"; +import { kindFromMime, mimeTypeFromFilePath } from "@openclaw/media-core/mime"; import type { OpenClawConfig } from "../config/types.js"; import { readLocalFileSafely } from "../infra/fs-safe.js"; -import { kindFromMime, mimeTypeFromFilePath } from "../media/mime.js"; import { DEFAULT_MAX_BYTES } from "./defaults.constants.js"; import { normalizeImageDescriptionInput } from "./image-input-normalize.js"; import { describeImageWithModel } from "./image-runtime.js"; diff --git a/src/media/audio-transcode.ts b/src/media/audio-transcode.ts index 3197e9c67263..11a9baec1f02 100644 --- a/src/media/audio-transcode.ts +++ b/src/media/audio-transcode.ts @@ -1,10 +1,10 @@ import { spawn } from "node:child_process"; import path from "node:path"; +import { basenameFromAnyPath } from "@openclaw/media-core/file-name"; import { writeExternalFileWithinRoot } from "../infra/fs-safe.js"; import { tempWorkspaceSync, withTempWorkspace } from "../infra/private-temp-workspace.js"; import { resolvePreferredOpenClawTmpDir } from "../infra/tmp-openclaw-dir.js"; import { runFfmpeg } from "./ffmpeg-exec.js"; -import { basenameFromAnyPath } from "./file-name.js"; const DEFAULT_OPUS_SAMPLE_RATE_HZ = 48_000; const DEFAULT_OPUS_BITRATE = "64k"; diff --git a/src/media/audio.ts b/src/media/audio.ts index 01aa4fdb28e3..54201d4a5d30 100644 --- a/src/media/audio.ts +++ b/src/media/audio.ts @@ -1,5 +1,5 @@ +import { getFileExtension, normalizeMimeType } from "@openclaw/media-core/mime"; import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; -import { getFileExtension, normalizeMimeType } from "./mime.js"; export const VOICE_MESSAGE_AUDIO_EXTENSIONS = new Set([".oga", ".ogg", ".opus", ".mp3", ".m4a"]); diff --git a/src/media/configured-max-bytes.ts b/src/media/configured-max-bytes.ts index 4018d9840325..ccf480aefa12 100644 --- a/src/media/configured-max-bytes.ts +++ b/src/media/configured-max-bytes.ts @@ -1,5 +1,5 @@ +import { maxBytesForKind, type MediaKind } from "@openclaw/media-core/constants"; import type { OpenClawConfig } from "../config/types.openclaw.js"; -import { maxBytesForKind, type MediaKind } from "./constants.js"; const MB = 1024 * 1024; diff --git a/src/media/fetch.ts b/src/media/fetch.ts index 2b4c9dd059a8..d3aa7ad9b1fb 100644 --- a/src/media/fetch.ts +++ b/src/media/fetch.ts @@ -1,3 +1,11 @@ +import { MAX_DOCUMENT_BYTES } from "@openclaw/media-core/constants"; +import { parseMediaContentLength } from "@openclaw/media-core/content-length"; +import { basenameFromAnyPath, extnameFromAnyPath } from "@openclaw/media-core/file-name"; +import { detectMime, extensionForMime } from "@openclaw/media-core/mime"; +import { + readResponseTextSnippet, + readResponseWithLimit, +} from "@openclaw/media-core/read-response-with-limit"; import { formatErrorMessage } from "../infra/errors.js"; import { fetchWithSsrFGuard, @@ -9,11 +17,6 @@ import { retryAsync, type RetryOptions } from "../infra/retry.js"; import { isAbortError, isTransientNetworkError } from "../infra/unhandled-rejections.js"; import { redactSensitiveText } from "../logging/redact.js"; import { resolveTimerTimeoutMs } from "../shared/number-coercion.js"; -import { MAX_DOCUMENT_BYTES } from "./constants.js"; -import { parseMediaContentLength } from "./content-length.js"; -import { basenameFromAnyPath, extnameFromAnyPath } from "./file-name.js"; -import { detectMime, extensionForMime } from "./mime.js"; -import { readResponseTextSnippet, readResponseWithLimit } from "./read-response-with-limit.js"; import { saveMediaBuffer, saveMediaStream, type SavedMedia } from "./store.js"; export const DEFAULT_FETCH_MEDIA_MAX_BYTES = MAX_DOCUMENT_BYTES; diff --git a/src/media/input-files.fetch-guard.test.ts b/src/media/input-files.fetch-guard.test.ts index f215e544a987..74bccf608c88 100644 --- a/src/media/input-files.fetch-guard.test.ts +++ b/src/media/input-files.fetch-guard.test.ts @@ -12,7 +12,7 @@ vi.mock("./media-services.js", () => ({ convertHeicToJpeg: (...args: unknown[]) => convertHeicToJpegMock(...args), })); -vi.mock("./mime.js", () => ({ +vi.mock("@openclaw/media-core/mime", () => ({ detectMime: (...args: unknown[]) => detectMimeMock(...args), })); diff --git a/src/media/input-files.ts b/src/media/input-files.ts index a0d2dcf62c6e..519c33a0ee2e 100644 --- a/src/media/input-files.ts +++ b/src/media/input-files.ts @@ -1,3 +1,7 @@ +import { canonicalizeBase64, estimateBase64DecodedBytes } from "@openclaw/media-core/base64"; +import { parseMediaContentLength } from "@openclaw/media-core/content-length"; +import { detectMime } from "@openclaw/media-core/mime"; +import { readResponseWithLimit } from "@openclaw/media-core/read-response-with-limit"; import { normalizeOptionalLowercaseString, normalizeOptionalString, @@ -6,12 +10,8 @@ import type { OpenClawConfig } from "../config/types.openclaw.js"; import { fetchWithSsrFGuard } from "../infra/net/fetch-guard.js"; import type { SsrFPolicy } from "../infra/net/ssrf.js"; import { logWarn } from "../logger.js"; -import { canonicalizeBase64, estimateBase64DecodedBytes } from "./base64.js"; -import { parseMediaContentLength } from "./content-length.js"; import { convertHeicToJpeg } from "./media-services.js"; -import { detectMime } from "./mime.js"; import { extractPdfContent, type PdfExtractedImage } from "./pdf-extract.js"; -import { readResponseWithLimit } from "./read-response-with-limit.js"; export type InputImageContent = PdfExtractedImage; diff --git a/src/media/local-media-access.ts b/src/media/local-media-access.ts index aad2d2cf1e1f..5abc0e9dc0ac 100644 --- a/src/media/local-media-access.ts +++ b/src/media/local-media-access.ts @@ -1,8 +1,8 @@ import fs from "node:fs/promises"; import path from "node:path"; +import { isInboundPathAllowed } from "@openclaw/media-core/inbound-path-policy"; import { assertNoWindowsNetworkPath } from "../infra/local-file-access.js"; import { isPathInside } from "../infra/path-guards.js"; -import { isInboundPathAllowed } from "./inbound-path-policy.js"; import { getDefaultMediaLocalRoots } from "./local-roots.js"; import { resolveInboundMediaReference } from "./media-reference.js"; diff --git a/src/media/local-roots.ts b/src/media/local-roots.ts index f4fce7134386..60f4d525f736 100644 --- a/src/media/local-roots.ts +++ b/src/media/local-roots.ts @@ -1,4 +1,5 @@ import path from "node:path"; +import { isPassThroughRemoteMediaSource } from "@openclaw/media-core/media-source-url"; import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; import { uniqueStrings } from "@openclaw/normalization-core/string-normalization"; import { resolveAgentWorkspaceDir } from "../agents/agent-scope.js"; @@ -11,7 +12,6 @@ import type { OpenClawConfig } from "../config/types.js"; import { safeFileURLToPath } from "../infra/local-file-access.js"; import { resolvePreferredOpenClawTmpDir } from "../infra/tmp-openclaw-dir.js"; import { resolveConfigDir, resolveUserPath } from "../utils.js"; -import { isPassThroughRemoteMediaSource } from "./media-source-url.js"; type BuildMediaLocalRootsOptions = { preferredTmpDir?: string; diff --git a/src/media/sniff-mime-from-base64.ts b/src/media/sniff-mime-from-base64.ts index 2c63f64e73f5..f24740f2419e 100644 --- a/src/media/sniff-mime-from-base64.ts +++ b/src/media/sniff-mime-from-base64.ts @@ -1,5 +1,5 @@ -import { canonicalizeBase64 } from "./base64.js"; -import { detectMime } from "./mime.js"; +import { canonicalizeBase64 } from "@openclaw/media-core/base64"; +import { detectMime } from "@openclaw/media-core/mime"; export async function sniffMimeFromBase64(base64: string): Promise { const trimmed = base64.trim(); diff --git a/src/media/store.test.ts b/src/media/store.test.ts index 747d670ec557..75b0ad32a7b3 100644 --- a/src/media/store.test.ts +++ b/src/media/store.test.ts @@ -889,8 +889,10 @@ describe("media store", () => { it("prefers header mime extension when sniffed mime lacks mapping", async () => { await withTempStore(async (_store, home) => { - vi.doMock("./mime.js", async () => { - const actual = await vi.importActual("./mime.js"); + vi.doMock("@openclaw/media-core/mime", async () => { + const actual = await vi.importActual( + "@openclaw/media-core/mime", + ); return { ...actual, detectMime: vi.fn(async () => "audio/opus"), @@ -909,7 +911,7 @@ describe("media store", () => { expect(path.extname(saved.path)).toBe(".ogg"); expect(saved.path.startsWith(home)).toBe(true); } finally { - vi.doUnmock("./mime.js"); + vi.doUnmock("@openclaw/media-core/mime"); } }); }); diff --git a/src/media/store.ts b/src/media/store.ts index 504b3fdf4bbc..0d9b4ec2c698 100644 --- a/src/media/store.ts +++ b/src/media/store.ts @@ -6,6 +6,12 @@ import { request as httpRequest } from "node:http"; import { request as httpsRequest } from "node:https"; import path from "node:path"; import { pipeline } from "node:stream/promises"; +import { + basenameFromAnyPath, + extnameFromAnyPath, + nameFromAnyPath, +} from "@openclaw/media-core/file-name"; +import { detectMime, extensionForMime } from "@openclaw/media-core/mime"; import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; import { fileStore } from "../infra/file-store.js"; import { sanitizeUntrustedFileName } from "../infra/fs-safe-advanced.js"; @@ -14,8 +20,6 @@ import { retainSafeHeadersForCrossOriginRedirect } from "../infra/net/redirect-h import { resolvePinnedHostname } from "../infra/net/ssrf.js"; import { writeSiblingTempFile } from "../infra/sibling-temp-file.js"; import { resolveConfigDir } from "../utils.js"; -import { basenameFromAnyPath, extnameFromAnyPath, nameFromAnyPath } from "./file-name.js"; -import { detectMime, extensionForMime } from "./mime.js"; import { isFsSafeError, readLocalFileSafely, type FsSafeLikeError } from "./store.runtime.js"; const resolveMediaDir = () => path.join(resolveConfigDir(), "media"); diff --git a/src/media/web-media.ts b/src/media/web-media.ts index 0fe1a8396ee3..7e190900d6a3 100644 --- a/src/media/web-media.ts +++ b/src/media/web-media.ts @@ -1,5 +1,15 @@ import { lstat, realpath } from "node:fs/promises"; import path from "node:path"; +import { maxBytesForKind, type MediaKind } from "@openclaw/media-core/constants"; +import { basenameFromAnyPath, extnameFromAnyPath } from "@openclaw/media-core/file-name"; +import { + detectMime, + extensionForMime, + getFileExtension, + kindFromMime, + mimeTypeFromFilePath, + normalizeMimeType, +} from "@openclaw/media-core/mime"; import { uniqueValues } from "@openclaw/normalization-core/string-normalization"; import { logVerbose, shouldLogVerbose } from "../globals.js"; import { formatErrorMessage } from "../infra/errors.js"; @@ -9,9 +19,7 @@ import type { PinnedDispatcherPolicy, SsrFPolicy } from "../infra/net/ssrf.js"; import { resolvePreferredOpenClawTmpDir } from "../infra/tmp-openclaw-dir.js"; import { getActivePluginRegistry } from "../plugins/runtime.js"; import { resolveUserPath } from "../utils.js"; -import { maxBytesForKind, type MediaKind } from "./constants.js"; import { readRemoteMediaBuffer } from "./fetch.js"; -import { basenameFromAnyPath, extnameFromAnyPath } from "./file-name.js"; import { assertLocalMediaAllowed, getDefaultLocalRoots, @@ -24,14 +32,6 @@ import { readImageMetadataFromHeader, readImageProbeFromHeader, } from "./media-services.js"; -import { - detectMime, - extensionForMime, - getFileExtension, - kindFromMime, - mimeTypeFromFilePath, - normalizeMimeType, -} from "./mime.js"; export { getDefaultLocalRoots, LocalMediaAccessError }; export type { LocalMediaAccessErrorCode }; diff --git a/src/music-generation/provider-assets.ts b/src/music-generation/provider-assets.ts index 924fe75ac226..5d73a8ed4e09 100644 --- a/src/music-generation/provider-assets.ts +++ b/src/music-generation/provider-assets.ts @@ -1,9 +1,9 @@ +import { maxBytesForKind } from "@openclaw/media-core/constants"; +import { extensionForMime } from "@openclaw/media-core/mime"; +import { readResponseWithLimit } from "@openclaw/media-core/read-response-with-limit"; import { isRecord } from "@openclaw/normalization-core/record-coerce"; import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; import { fetchProviderDownloadResponse } from "../media-understanding/shared.js"; -import { maxBytesForKind } from "../media/constants.js"; -import { extensionForMime } from "../media/mime.js"; -import { readResponseWithLimit } from "../media/read-response-with-limit.js"; import type { GeneratedMusicAsset } from "./types.js"; export type GeneratedMusicFileCandidate = { diff --git a/src/plugin-sdk/acp-runtime-backend.ts b/src/plugin-sdk/acp-runtime-backend.ts index 5c03637a4385..6539bbbdeae5 100644 --- a/src/plugin-sdk/acp-runtime-backend.ts +++ b/src/plugin-sdk/acp-runtime-backend.ts @@ -29,7 +29,7 @@ export type { AcpRuntimeTurnResult, AcpRuntimeTurnResultError, AcpSessionUpdateTag, -} from "../acp/runtime/types.js"; +} from "@openclaw/acp-core/runtime/types"; let dispatchAcpRuntimePromise: Promise< typeof import("../auto-reply/reply/dispatch-acp.runtime.js") diff --git a/src/plugin-sdk/acp-runtime.ts b/src/plugin-sdk/acp-runtime.ts index bb3399adc78f..485470cb9ddc 100644 --- a/src/plugin-sdk/acp-runtime.ts +++ b/src/plugin-sdk/acp-runtime.ts @@ -26,7 +26,7 @@ export type { AcpRuntimeTurnResult, AcpRuntimeTurnResultError, AcpSessionUpdateTag, -} from "../acp/runtime/types.js"; +} from "@openclaw/acp-core/runtime/types"; export { readAcpSessionEntry } from "../acp/runtime/session-meta.js"; export type { AcpSessionStoreEntry } from "../acp/runtime/session-meta.js"; export { tryDispatchAcpReplyHook } from "./acp-runtime-backend.js"; diff --git a/src/plugin-sdk/acpx.ts b/src/plugin-sdk/acpx.ts index 6430448b68be..37b0dd6d6b10 100644 --- a/src/plugin-sdk/acpx.ts +++ b/src/plugin-sdk/acpx.ts @@ -17,7 +17,7 @@ export type { AcpRuntimeTurnResult, AcpRuntimeTurnResultError, AcpSessionUpdateTag, -} from "../acp/runtime/types.js"; +} from "@openclaw/acp-core/runtime/types"; export type { OpenClawPluginApi, OpenClawPluginConfigSchema, diff --git a/src/plugin-sdk/agent-harness-runtime.ts b/src/plugin-sdk/agent-harness-runtime.ts index 68698151463b..a2293d232419 100644 --- a/src/plugin-sdk/agent-harness-runtime.ts +++ b/src/plugin-sdk/agent-harness-runtime.ts @@ -203,7 +203,7 @@ export async function detectAndLoadAgentHarnessPromptImages(params: { await Promise.all([ import("../agents/image-sanitization.js"), import("../agents/embedded-agent-runner/run/images.js"), - import("../media/constants.js"), + import("@openclaw/media-core/constants"), ]); return detectAndLoadPromptImages({ diff --git a/src/plugin-sdk/channel-inbound.ts b/src/plugin-sdk/channel-inbound.ts index 8a645dd8d0ac..8c3c9f2c4444 100644 --- a/src/plugin-sdk/channel-inbound.ts +++ b/src/plugin-sdk/channel-inbound.ts @@ -184,4 +184,4 @@ export { isTextSlashCommandTurn, } from "../auto-reply/command-turn-context.js"; export type { CommandTurnContext } from "../auto-reply/command-turn-context.js"; -export { mergeInboundPathRoots } from "../media/inbound-path-policy.js"; +export { mergeInboundPathRoots } from "@openclaw/media-core/inbound-path-policy"; diff --git a/src/plugin-sdk/inline-image-data-url-runtime.ts b/src/plugin-sdk/inline-image-data-url-runtime.ts index 45c8ad9e8750..401d2eb15095 100644 --- a/src/plugin-sdk/inline-image-data-url-runtime.ts +++ b/src/plugin-sdk/inline-image-data-url-runtime.ts @@ -2,4 +2,4 @@ export { INLINE_IMAGE_DATA_URL_PREFIX, sanitizeInlineImageDataUrl, sniffInlineImageMime, -} from "../media/inline-image-data-url.js"; +} from "@openclaw/media-core/inline-image-data-url"; diff --git a/src/plugin-sdk/media-mime.ts b/src/plugin-sdk/media-mime.ts index b17fc67f8d50..2813eb9f4c8b 100644 --- a/src/plugin-sdk/media-mime.ts +++ b/src/plugin-sdk/media-mime.ts @@ -6,5 +6,5 @@ export { getFileExtension, mimeTypeFromFilePath, normalizeMimeType, -} from "../media/mime.js"; -export { mediaKindFromMime, type MediaKind } from "../media/constants.js"; +} from "@openclaw/media-core/mime"; +export { mediaKindFromMime, type MediaKind } from "@openclaw/media-core/constants"; diff --git a/src/plugin-sdk/media-runtime.ts b/src/plugin-sdk/media-runtime.ts index ac3ffa87982d..2758aef5bad6 100644 --- a/src/plugin-sdk/media-runtime.ts +++ b/src/plugin-sdk/media-runtime.ts @@ -4,12 +4,12 @@ */ export * from "../media/audio.js"; -export * from "../media/base64.js"; -export * from "../media/content-length.js"; -export * from "../media/constants.js"; +export * from "@openclaw/media-core/base64"; +export * from "@openclaw/media-core/content-length"; +export * from "@openclaw/media-core/constants"; export * from "../media/fetch.js"; export * from "../media/ffmpeg-limits.js"; -export * from "../media/inbound-path-policy.js"; +export * from "@openclaw/media-core/inbound-path-policy"; export * from "../media/load-options.js"; export * from "../media/local-media-access.js"; export * from "../media/local-roots.js"; @@ -40,13 +40,13 @@ export { type MediaExecOptions, type VideoDimensions, } from "../media/media-services.js"; -export * from "../media/mime.js"; +export * from "@openclaw/media-core/mime"; export * from "../media/outbound-attachment.js"; export * from "../media/png-encode.ts"; export * from "../media/qr-image.ts"; export * from "../media/qr-terminal.ts"; -export * from "../media/read-byte-stream-with-limit.js"; -export * from "../media/read-response-with-limit.js"; +export * from "@openclaw/media-core/read-byte-stream-with-limit"; +export * from "@openclaw/media-core/read-response-with-limit"; export * from "../media/store.js"; export * from "../media/temp-files.js"; export { resolveChannelMediaMaxBytes } from "../channels/plugins/media-limits.js"; diff --git a/src/plugin-sdk/memory-core-host-engine-foundation.ts b/src/plugin-sdk/memory-core-host-engine-foundation.ts index dd926e611942..acf881bbe666 100644 --- a/src/plugin-sdk/memory-core-host-engine-foundation.ts +++ b/src/plugin-sdk/memory-core-host-engine-foundation.ts @@ -34,7 +34,7 @@ export type { export type { MemorySearchConfig } from "../config/types.tools.js"; export { root } from "../infra/fs-safe.js"; export { createSubsystemLogger } from "../logging/subsystem.js"; -export { detectMime } from "../media/mime.js"; +export { detectMime } from "@openclaw/media-core/mime"; export { onSessionTranscriptUpdate } from "../sessions/transcript-events.js"; export { resolveGlobalSingleton } from "../shared/global-singleton.js"; export { runTasksWithConcurrency } from "../utils/run-with-concurrency.js"; diff --git a/src/plugin-sdk/response-limit-runtime.ts b/src/plugin-sdk/response-limit-runtime.ts index 569fa905ce76..188d2e2f50bc 100644 --- a/src/plugin-sdk/response-limit-runtime.ts +++ b/src/plugin-sdk/response-limit-runtime.ts @@ -1,4 +1,4 @@ // Narrow response-size reader for plugins that download bounded HTTP bodies. -export { readByteStreamWithLimit } from "../media/read-byte-stream-with-limit.js"; -export { readResponseWithLimit } from "../media/read-response-with-limit.js"; +export { readByteStreamWithLimit } from "@openclaw/media-core/read-byte-stream-with-limit"; +export { readResponseWithLimit } from "@openclaw/media-core/read-response-with-limit"; diff --git a/src/plugins/contracts/extension-package-project-boundaries.test.ts b/src/plugins/contracts/extension-package-project-boundaries.test.ts index f7bfc125ac84..770da5254902 100644 --- a/src/plugins/contracts/extension-package-project-boundaries.test.ts +++ b/src/plugins/contracts/extension-package-project-boundaries.test.ts @@ -195,9 +195,11 @@ describe("opt-in extension package boundaries", () => { expect(tsconfig.compilerOptions?.rootDir).toBe("../.."); expect(tsconfig.include).toEqual([ "../../packages/markdown-core/src/**/*.ts", + "../../packages/media-core/src/**/*.ts", "../../packages/media-generation-core/src/**/*.ts", "../../packages/model-catalog-core/src/**/*.ts", "../../packages/normalization-core/src/**/*.ts", + "../../packages/acp-core/src/**/*.ts", "../../packages/terminal-core/src/**/*.ts", "../../src/plugin-sdk/**/*.ts", "../../src/video-generation/dashscope-compatible.ts", diff --git a/src/plugins/contracts/session-attachments.contract.test.ts b/src/plugins/contracts/session-attachments.contract.test.ts index 535b38e1f675..3539b7aeec6b 100644 --- a/src/plugins/contracts/session-attachments.contract.test.ts +++ b/src/plugins/contracts/session-attachments.contract.test.ts @@ -1,5 +1,6 @@ import * as fs from "node:fs/promises"; import path from "node:path"; +import { FILE_TYPE_SNIFF_MAX_BYTES } from "@openclaw/media-core/mime"; import { createPluginRegistryFixture, registerTestPlugin, @@ -8,7 +9,6 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { updateSessionStore, type SessionEntry } from "../../config/sessions.js"; import { withTempConfig } from "../../gateway/test-temp-config.js"; import { resolvePreferredOpenClawTmpDir } from "../../infra/tmp-openclaw-dir.js"; -import { FILE_TYPE_SNIFF_MAX_BYTES } from "../../media/mime.js"; import { createOutboundTestPlugin, createTestRegistry } from "../../test-utils/channel-plugins.js"; import { attachmentProbeFs, diff --git a/src/plugins/host-hook-attachments.ts b/src/plugins/host-hook-attachments.ts index a975d33375a6..7e94bd32c418 100644 --- a/src/plugins/host-hook-attachments.ts +++ b/src/plugins/host-hook-attachments.ts @@ -1,5 +1,10 @@ import * as fsPromises from "node:fs/promises"; import { lstat } from "node:fs/promises"; +import { + detectMime, + FILE_TYPE_SNIFF_MAX_BYTES, + normalizeMimeType, +} from "@openclaw/media-core/mime"; import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; import { resolveAgentWorkspaceDir } from "../agents/agent-scope.js"; import { resolvePathFromInput } from "../agents/path-policy.js"; @@ -7,7 +12,6 @@ import { resolveWorkspaceRoot } from "../agents/workspace-dir.js"; import { extractDeliveryInfo } from "../config/sessions/delivery-info.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { formatErrorMessage } from "../infra/errors.js"; -import { detectMime, FILE_TYPE_SNIFF_MAX_BYTES, normalizeMimeType } from "../media/mime.js"; import { resolveAgentIdFromSessionKey } from "../routing/session-key.js"; import { isDeliverableMessageChannel, normalizeMessageChannel } from "../utils/message-channel.js"; import type { diff --git a/src/plugins/plugin-sdk-native-resolver.test.ts b/src/plugins/plugin-sdk-native-resolver.test.ts index c95fa9f26168..ce3a27ffe3e8 100644 --- a/src/plugins/plugin-sdk-native-resolver.test.ts +++ b/src/plugins/plugin-sdk-native-resolver.test.ts @@ -86,6 +86,17 @@ function writeNormalizationCoreSource(root: string): string { return sourcePath; } +function writeInternalCorePackageSource( + root: string, + packageDir: string, + sourceFile: string, +): string { + const sourcePath = path.join(root, "packages", packageDir, "src", sourceFile); + fs.mkdirSync(path.dirname(sourcePath), { recursive: true }); + fs.writeFileSync(sourcePath, "export {};\n", "utf8"); + return sourcePath; +} + function addFakePluginSdkDistExport(root: string, subpath: string): string { const packageJsonPath = path.join(root, "package.json"); const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")) as { @@ -372,6 +383,12 @@ describe("installOpenClawPluginSdkNativeResolver", () => { const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-sdk-native-core-internal-")); const { loaderModulePath } = writeFakeOpenClawPackage(root); const normalizationSource = writeNormalizationCoreSource(root); + const mediaCoreSource = writeInternalCorePackageSource(root, "media-core", "mime.ts"); + const acpCoreSource = writeInternalCorePackageSource( + root, + "acp-core", + path.join("runtime", "types.ts"), + ); const externalPluginEntry = writeExternalPluginEntry(path.join(root, "external-plugin")); const coreSourceParent = path.join(root, "src", "config", "plugin-web-search-config.ts"); fs.mkdirSync(path.dirname(coreSourceParent), { recursive: true }); @@ -384,12 +401,22 @@ describe("installOpenClawPluginSdkNativeResolver", () => { }); expect(installedAliases).toContain("@openclaw/normalization-core/string-coerce"); + expect(installedAliases).toContain("@openclaw/media-core/mime"); + expect(installedAliases).toContain("@openclaw/acp-core/runtime/types"); const requireFromCoreSource = createRequire(coreSourceParent); const requireFromPlugin = createRequire(externalPluginEntry); expect( fs.realpathSync(requireFromCoreSource.resolve("@openclaw/normalization-core/string-coerce")), ).toBe(fs.realpathSync(normalizationSource)); + expect(fs.realpathSync(requireFromCoreSource.resolve("@openclaw/media-core/mime"))).toBe( + fs.realpathSync(mediaCoreSource), + ); + expect(fs.realpathSync(requireFromCoreSource.resolve("@openclaw/acp-core/runtime/types"))).toBe( + fs.realpathSync(acpCoreSource), + ); expect(() => requireFromPlugin.resolve("@openclaw/normalization-core/string-coerce")).toThrow(); + expect(() => requireFromPlugin.resolve("@openclaw/media-core/mime")).toThrow(); + expect(() => requireFromPlugin.resolve("@openclaw/acp-core/runtime/types")).toThrow(); }); it("does not register source-only SDK subpaths for native resolution", () => { diff --git a/src/plugins/plugin-sdk-native-resolver.ts b/src/plugins/plugin-sdk-native-resolver.ts index bf2a1701a892..fbe2eb68ae54 100644 --- a/src/plugins/plugin-sdk-native-resolver.ts +++ b/src/plugins/plugin-sdk-native-resolver.ts @@ -57,6 +57,33 @@ const INTERNAL_CORE_PACKAGE_ALIASES = [ ["string-normalization", "string-normalization.ts"], ], }, + { + packageName: "@openclaw/media-core", + packageDir: "media-core", + subpaths: [ + ["", "index.ts"], + ["base64", "base64.ts"], + ["constants", "constants.ts"], + ["content-length", "content-length.ts"], + ["file-name", "file-name.ts"], + ["inbound-path-policy", "inbound-path-policy.ts"], + ["inline-image-data-url", "inline-image-data-url.ts"], + ["media-source-url", "media-source-url.ts"], + ["mime", "mime.ts"], + ["read-byte-stream-with-limit", "read-byte-stream-with-limit.ts"], + ["read-response-with-limit", "read-response-with-limit.ts"], + ], + }, + { + packageName: "@openclaw/acp-core", + packageDir: "acp-core", + subpaths: [ + ["", "index.ts"], + ["normalize-text", "normalize-text.ts"], + ["record-shared", "record-shared.ts"], + ["runtime/types", path.join("runtime", "types.ts")], + ], + }, ] as const; const pluginSdkNativeAliases = new Map(); let installed = false; diff --git a/src/plugins/runtime/runtime-media.ts b/src/plugins/runtime/runtime-media.ts index 725b4b2fefba..7fd0613177fc 100644 --- a/src/plugins/runtime/runtime-media.ts +++ b/src/plugins/runtime/runtime-media.ts @@ -1,7 +1,7 @@ +import { mediaKindFromMime } from "@openclaw/media-core/constants"; +import { detectMime } from "@openclaw/media-core/mime"; import { isVoiceCompatibleAudio } from "../../media/audio.js"; -import { mediaKindFromMime } from "../../media/constants.js"; import { getImageMetadata, resizeToJpeg } from "../../media/media-services.js"; -import { detectMime } from "../../media/mime.js"; import { loadWebMedia } from "../../media/web-media.js"; import type { PluginRuntime } from "./types.js"; diff --git a/src/plugins/runtime/types-core.ts b/src/plugins/runtime/types-core.ts index dc9fb755c673..b6cf629030b4 100644 --- a/src/plugins/runtime/types-core.ts +++ b/src/plugins/runtime/types-core.ts @@ -240,8 +240,8 @@ export type PluginRuntimeCore = { }; media: { loadWebMedia: typeof import("../../media/web-media.js").loadWebMedia; - detectMime: typeof import("../../media/mime.js").detectMime; - mediaKindFromMime: typeof import("../../media/constants.js").mediaKindFromMime; + detectMime: typeof import("@openclaw/media-core/mime").detectMime; + mediaKindFromMime: typeof import("@openclaw/media-core/constants").mediaKindFromMime; isVoiceCompatibleAudio: typeof import("../../media/audio.js").isVoiceCompatibleAudio; getImageMetadata: typeof import("../../media/media-services.js").getImageMetadata; resizeToJpeg: typeof import("../../media/media-services.js").resizeToJpeg; diff --git a/src/plugins/sdk-alias.test.ts b/src/plugins/sdk-alias.test.ts index ab651893d413..5169fe540731 100644 --- a/src/plugins/sdk-alias.test.ts +++ b/src/plugins/sdk-alias.test.ts @@ -1462,6 +1462,42 @@ describe("plugin sdk alias helpers", () => { srcFile: "index.ts", distFile: "index.mjs", }); + const mediaCore = writeWorkspacePackageEntry({ + root: fixture.root, + packageDir: "media-core", + srcFile: "index.ts", + distFile: "index.mjs", + }); + const mediaCoreMime = writeWorkspacePackageEntry({ + root: fixture.root, + packageDir: "media-core", + srcFile: "mime.ts", + distFile: "mime.mjs", + }); + const acpCore = writeWorkspacePackageEntry({ + root: fixture.root, + packageDir: "acp-core", + srcFile: "index.ts", + distFile: "index.mjs", + }); + const acpCoreRuntimeTypes = writeWorkspacePackageEntry({ + root: fixture.root, + packageDir: "acp-core", + srcFile: path.join("runtime", "types.ts"), + distFile: path.join("runtime", "types.mjs"), + }); + const normalizationCore = writeWorkspacePackageEntry({ + root: fixture.root, + packageDir: "normalization-core", + srcFile: "index.ts", + distFile: "index.mjs", + }); + const normalizationStringCoerce = writeWorkspacePackageEntry({ + root: fixture.root, + packageDir: "normalization-core", + srcFile: "string-coerce.ts", + distFile: "string-coerce.mjs", + }); const markdownCore = writeWorkspacePackageEntry({ root: fixture.root, packageDir: "markdown-core", @@ -1512,6 +1548,12 @@ describe("plugin sdk alias helpers", () => { fs.rmSync(markdownCoreTables.distFile); fs.rmSync(mediaGenerationCore.distFile); fs.rmSync(mediaGenerationModelRef.distFile); + fs.rmSync(mediaCore.distFile); + fs.rmSync(mediaCoreMime.distFile); + fs.rmSync(acpCore.distFile); + fs.rmSync(acpCoreRuntimeTypes.distFile); + fs.rmSync(normalizationCore.distFile); + fs.rmSync(normalizationStringCoerce.distFile); fs.rmSync(terminalCore.distFile); fs.rmSync(terminalCoreTheme.distFile); fs.rmSync(netPolicy.distFile); @@ -1550,6 +1592,24 @@ describe("plugin sdk alias helpers", () => { expect(fs.realpathSync(aliases["@openclaw/media-generation-core/model-ref"] ?? "")).toBe( fs.realpathSync(mediaGenerationModelRef.srcFile), ); + expect(fs.realpathSync(aliases["@openclaw/media-core"] ?? "")).toBe( + fs.realpathSync(mediaCore.srcFile), + ); + expect(fs.realpathSync(aliases["@openclaw/media-core/mime"] ?? "")).toBe( + fs.realpathSync(mediaCoreMime.srcFile), + ); + expect(fs.realpathSync(aliases["@openclaw/acp-core"] ?? "")).toBe( + fs.realpathSync(acpCore.srcFile), + ); + expect(fs.realpathSync(aliases["@openclaw/acp-core/runtime/types"] ?? "")).toBe( + fs.realpathSync(acpCoreRuntimeTypes.srcFile), + ); + expect(fs.realpathSync(aliases["@openclaw/normalization-core"] ?? "")).toBe( + fs.realpathSync(normalizationCore.srcFile), + ); + expect(fs.realpathSync(aliases["@openclaw/normalization-core/string-coerce"] ?? "")).toBe( + fs.realpathSync(normalizationStringCoerce.srcFile), + ); expect(fs.realpathSync(aliases["@openclaw/terminal-core"] ?? "")).toBe( fs.realpathSync(terminalCore.srcFile), ); @@ -1587,6 +1647,43 @@ describe("plugin sdk alias helpers", () => { srcFile: "catalog.ts", distFile: "catalog.mjs", }); + writeWorkspacePackageEntry({ + root: fixture.root, + packageDir: "media-core", + srcFile: "read-response-with-limit.ts", + distFile: "read-response-with-limit.mjs", + }); + const mediaCoreRootDistFile = path.join( + fixture.root, + "dist", + "media-core", + "read-response-with-limit.js", + ); + mkdirSafeDir(path.dirname(mediaCoreRootDistFile)); + fs.writeFileSync(mediaCoreRootDistFile, "export {};\n", "utf-8"); + writeWorkspacePackageEntry({ + root: fixture.root, + packageDir: "acp-core", + srcFile: "normalize-text.ts", + distFile: "normalize-text.mjs", + }); + const acpCoreRootDistFile = path.join(fixture.root, "dist", "acp-core", "normalize-text.js"); + mkdirSafeDir(path.dirname(acpCoreRootDistFile)); + fs.writeFileSync(acpCoreRootDistFile, "export {};\n", "utf-8"); + writeWorkspacePackageEntry({ + root: fixture.root, + packageDir: "normalization-core", + srcFile: "record-coerce.ts", + distFile: "record-coerce.mjs", + }); + const normalizationCoreRootDistFile = path.join( + fixture.root, + "dist", + "normalization-core", + "record-coerce.js", + ); + mkdirSafeDir(path.dirname(normalizationCoreRootDistFile)); + fs.writeFileSync(normalizationCoreRootDistFile, "export {};\n", "utf-8"); const markdownCore = writeWorkspacePackageEntry({ root: fixture.root, packageDir: "markdown-core", @@ -1635,6 +1732,15 @@ describe("plugin sdk alias helpers", () => { expect(fs.realpathSync(aliases["@openclaw/media-generation-core/catalog"] ?? "")).toBe( fs.realpathSync(mediaGenerationCore.distFile), ); + expect(fs.realpathSync(aliases["@openclaw/media-core/read-response-with-limit"] ?? "")).toBe( + fs.realpathSync(mediaCoreRootDistFile), + ); + expect(fs.realpathSync(aliases["@openclaw/acp-core/normalize-text"] ?? "")).toBe( + fs.realpathSync(acpCoreRootDistFile), + ); + expect(fs.realpathSync(aliases["@openclaw/normalization-core/record-coerce"] ?? "")).toBe( + fs.realpathSync(normalizationCoreRootDistFile), + ); expect(fs.realpathSync(aliases["@openclaw/terminal-core/links"] ?? "")).toBe( fs.realpathSync(terminalCoreRootDistFile), ); diff --git a/src/plugins/sdk-alias.ts b/src/plugins/sdk-alias.ts index 03716da18089..59432bfd3286 100644 --- a/src/plugins/sdk-alias.ts +++ b/src/plugins/sdk-alias.ts @@ -670,6 +670,146 @@ const WORKSPACE_PACKAGE_ALIAS_ENTRIES = [ srcFile: "normalization.ts", distFile: "normalization.mjs", }, + { + packageName: "@openclaw/media-core", + packageDir: "media-core", + subpath: "", + srcFile: "index.ts", + distFile: "index.mjs", + }, + { + packageName: "@openclaw/media-core", + packageDir: "media-core", + subpath: "base64", + srcFile: "base64.ts", + distFile: "base64.mjs", + }, + { + packageName: "@openclaw/media-core", + packageDir: "media-core", + subpath: "constants", + srcFile: "constants.ts", + distFile: "constants.mjs", + }, + { + packageName: "@openclaw/media-core", + packageDir: "media-core", + subpath: "content-length", + srcFile: "content-length.ts", + distFile: "content-length.mjs", + }, + { + packageName: "@openclaw/media-core", + packageDir: "media-core", + subpath: "file-name", + srcFile: "file-name.ts", + distFile: "file-name.mjs", + }, + { + packageName: "@openclaw/media-core", + packageDir: "media-core", + subpath: "inbound-path-policy", + srcFile: "inbound-path-policy.ts", + distFile: "inbound-path-policy.mjs", + }, + { + packageName: "@openclaw/media-core", + packageDir: "media-core", + subpath: "inline-image-data-url", + srcFile: "inline-image-data-url.ts", + distFile: "inline-image-data-url.mjs", + }, + { + packageName: "@openclaw/media-core", + packageDir: "media-core", + subpath: "media-source-url", + srcFile: "media-source-url.ts", + distFile: "media-source-url.mjs", + }, + { + packageName: "@openclaw/media-core", + packageDir: "media-core", + subpath: "mime", + srcFile: "mime.ts", + distFile: "mime.mjs", + }, + { + packageName: "@openclaw/media-core", + packageDir: "media-core", + subpath: "read-byte-stream-with-limit", + srcFile: "read-byte-stream-with-limit.ts", + distFile: "read-byte-stream-with-limit.mjs", + }, + { + packageName: "@openclaw/media-core", + packageDir: "media-core", + subpath: "read-response-with-limit", + srcFile: "read-response-with-limit.ts", + distFile: "read-response-with-limit.mjs", + }, + { + packageName: "@openclaw/acp-core", + packageDir: "acp-core", + subpath: "", + srcFile: "index.ts", + distFile: "index.mjs", + }, + { + packageName: "@openclaw/acp-core", + packageDir: "acp-core", + subpath: "normalize-text", + srcFile: "normalize-text.ts", + distFile: "normalize-text.mjs", + }, + { + packageName: "@openclaw/acp-core", + packageDir: "acp-core", + subpath: "record-shared", + srcFile: "record-shared.ts", + distFile: "record-shared.mjs", + }, + { + packageName: "@openclaw/acp-core", + packageDir: "acp-core", + subpath: "runtime/types", + srcFile: path.join("runtime", "types.ts"), + distFile: path.join("runtime", "types.mjs"), + }, + { + packageName: "@openclaw/normalization-core", + packageDir: "normalization-core", + subpath: "", + srcFile: "index.ts", + distFile: "index.mjs", + }, + { + packageName: "@openclaw/normalization-core", + packageDir: "normalization-core", + subpath: "number-coercion", + srcFile: "number-coercion.ts", + distFile: "number-coercion.mjs", + }, + { + packageName: "@openclaw/normalization-core", + packageDir: "normalization-core", + subpath: "record-coerce", + srcFile: "record-coerce.ts", + distFile: "record-coerce.mjs", + }, + { + packageName: "@openclaw/normalization-core", + packageDir: "normalization-core", + subpath: "string-coerce", + srcFile: "string-coerce.ts", + distFile: "string-coerce.mjs", + }, + { + packageName: "@openclaw/normalization-core", + packageDir: "normalization-core", + subpath: "string-normalization", + srcFile: "string-normalization.ts", + distFile: "string-normalization.mjs", + }, { packageName: "@openclaw/terminal-core", packageDir: "terminal-core", @@ -888,6 +1028,12 @@ const WORKSPACE_PACKAGE_ALIAS_ENTRIES = [ distFile: "provider-model-id-normalize.mjs", }, ] as const; +const ROOT_PACKAGED_WORKSPACE_PACKAGE_DIRS = new Set([ + "acp-core", + "media-core", + "normalization-core", + "terminal-core", +]); function isUsableDistPluginSdkArtifact(candidate: string): boolean { if (!fs.existsSync(candidate)) { @@ -1098,12 +1244,12 @@ function resolveWorkspacePackageAliasMap(params: { const candidates = kind === "dist" ? [ - ...(entry.packageName === "@openclaw/terminal-core" + ...(ROOT_PACKAGED_WORKSPACE_PACKAGE_DIRS.has(entry.packageDir) ? [ path.join( packageRoot, "dist", - "terminal-core", + entry.packageDir, entry.distFile.replace(/\.mjs$/u, ".js"), ), ] diff --git a/src/sessions/user-turn-transcript.ts b/src/sessions/user-turn-transcript.ts index c6ad5a92b30a..d9e6b1f878a6 100644 --- a/src/sessions/user-turn-transcript.ts +++ b/src/sessions/user-turn-transcript.ts @@ -1,7 +1,7 @@ import path from "node:path"; +import { mimeTypeFromFilePath } from "@openclaw/media-core/mime"; import type { AgentMessage } from "../agents/runtime/index.js"; import { appendSessionTranscriptMessage } from "../config/sessions/transcript-append.js"; -import { mimeTypeFromFilePath } from "../media/mime.js"; import { applyInputProvenanceToUserMessage, type InputProvenance, diff --git a/src/video-generation/dashscope-compatible.ts b/src/video-generation/dashscope-compatible.ts index b65983024ddd..4d1844b68c6f 100644 --- a/src/video-generation/dashscope-compatible.ts +++ b/src/video-generation/dashscope-compatible.ts @@ -1,3 +1,4 @@ +import { readResponseWithLimit } from "@openclaw/media-core/read-response-with-limit"; import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce"; import { uniqueStrings } from "@openclaw/normalization-core/string-normalization"; import { @@ -12,7 +13,6 @@ import { type ProviderOperationTimeoutMs, } from "openclaw/plugin-sdk/provider-http"; import { resolveGeneratedMediaMaxBytes } from "../media/configured-max-bytes.js"; -import { readResponseWithLimit } from "../media/read-response-with-limit.js"; import type { GeneratedVideoAsset, VideoGenerationProviderCapabilities, diff --git a/test/scripts/lint-suppressions.test.ts b/test/scripts/lint-suppressions.test.ts index a6a6bfd38204..d50e1a073ed8 100644 --- a/test/scripts/lint-suppressions.test.ts +++ b/test/scripts/lint-suppressions.test.ts @@ -184,11 +184,8 @@ describe("production lint suppressions", () => { "extensions/feishu/src/bitable.ts|typescript/no-unnecessary-type-parameters|1", "extensions/matrix/src/onboarding.test-harness.ts|typescript/no-unnecessary-type-parameters|1", "extensions/slack/src/monitor/provider-support.ts|typescript/no-unnecessary-type-parameters|1", - "extensions/telegram/src/telegram-ingress-worker.runtime.ts|unicorn/require-post-message-target-origin|1", - "extensions/telegram/src/telegram-ingress-worker.ts|unicorn/require-post-message-target-origin|1", "scripts/lib/extension-package-boundary.ts|typescript/no-unnecessary-type-parameters|1", "scripts/lib/plugin-npm-release.ts|typescript/no-unnecessary-type-parameters|1", - "src/agents/code-mode.worker.ts|unicorn/require-post-message-target-origin|1", "src/channels/plugins/channel-runtime-surface.types.ts|typescript/no-unnecessary-type-parameters|1", "src/channels/plugins/contracts/test-helpers.ts|typescript/no-unnecessary-type-parameters|1", "src/channels/plugins/types.plugin.ts|typescript/no-explicit-any|1", @@ -196,7 +193,6 @@ describe("production lint suppressions", () => { "src/cli/command-options.ts|typescript/no-unnecessary-type-parameters|1", "src/cli/plugins-cli-test-helpers.ts|typescript/no-unnecessary-type-parameters|1", "src/cli/test-runtime-capture.ts|typescript/no-unnecessary-type-parameters|1", - "src/config/types.channels.ts|typescript/no-explicit-any|1", "src/gateway/test-helpers.server.ts|typescript/no-unnecessary-type-parameters|1", "src/hooks/module-loader.ts|typescript/no-unnecessary-type-parameters|1", "src/infra/channel-runtime-context.ts|typescript/no-unnecessary-type-parameters|1", @@ -227,7 +223,6 @@ describe("production lint suppressions", () => { "src/test-utils/vitest-mock-fn.ts|typescript/no-explicit-any|1", "src/utils.ts|typescript/no-unnecessary-type-parameters|1", "src/version.ts|eslint/no-underscore-dangle|1", - "ui/src/ui/views/overview-log-tail.ts|no-control-regex|1", ]); }); @@ -241,10 +236,6 @@ describe("production lint suppressions", () => { file: "src/channels/plugins/types.plugin.ts", rule: "typescript/no-explicit-any", }, - { - file: "src/config/types.channels.ts", - rule: "typescript/no-explicit-any", - }, { file: "src/test-utils/vitest-mock-fn.ts", rule: "typescript/no-explicit-any", diff --git a/test/vitest-scoped-config.test.ts b/test/vitest-scoped-config.test.ts index e90d0df9a0da..bef9701369bc 100644 --- a/test/vitest-scoped-config.test.ts +++ b/test/vitest-scoped-config.test.ts @@ -162,6 +162,19 @@ describe("resolveVitestIsolation", () => { } }); + it("aliases private core packages to source for clean checkout tests", () => { + expect(findAlias(sharedVitestConfig.resolve.alias, "@openclaw/media-core/mime")).toEqual({ + find: "@openclaw/media-core/mime", + replacement: path.join(process.cwd(), "packages", "media-core", "src", "mime.ts"), + }); + expect(findAlias(sharedVitestConfig.resolve.alias, "@openclaw/acp-core/runtime/types")).toEqual( + { + find: "@openclaw/acp-core/runtime/types", + replacement: path.join(process.cwd(), "packages", "acp-core", "src", "runtime", "types.ts"), + }, + ); + }); + it("defaults shared scoped configs to the non-isolated runner", () => { expect(resolveVitestIsolation({})).toBe(false); }); diff --git a/test/vitest/vitest.shared.config.ts b/test/vitest/vitest.shared.config.ts index be6f076048ab..f5553ce54f6b 100644 --- a/test/vitest/vitest.shared.config.ts +++ b/test/vitest/vitest.shared.config.ts @@ -82,6 +82,21 @@ function hasWorkerOverride(env: Record): boolean { return Boolean((env.OPENCLAW_VITEST_MAX_WORKERS ?? env.OPENCLAW_TEST_WORKERS)?.trim()); } +function sourcePackageAlias(packageId: string, subpath?: string) { + return { + find: `@openclaw/${packageId}${subpath ? `/${subpath}` : ""}`, + replacement: path.join( + repoRoot, + "packages", + packageId, + "src", + ...(subpath ? subpath.split("/") : ["index"]).map((part, index, parts) => + index === parts.length - 1 ? `${part}.ts` : part, + ), + ), + }; +} + export function resolveSharedVitestWorkerConfig(params: { env?: Record; isCI?: boolean; @@ -375,6 +390,21 @@ export const sharedVitestConfig = { find: "@openclaw/normalization-core", replacement: path.join(repoRoot, "packages", "normalization-core", "src", "index.ts"), }, + sourcePackageAlias("media-core", "base64"), + sourcePackageAlias("media-core", "constants"), + sourcePackageAlias("media-core", "content-length"), + sourcePackageAlias("media-core", "file-name"), + sourcePackageAlias("media-core", "inbound-path-policy"), + sourcePackageAlias("media-core", "inline-image-data-url"), + sourcePackageAlias("media-core", "media-source-url"), + sourcePackageAlias("media-core", "mime"), + sourcePackageAlias("media-core", "read-byte-stream-with-limit"), + sourcePackageAlias("media-core", "read-response-with-limit"), + sourcePackageAlias("media-core"), + sourcePackageAlias("acp-core", "normalize-text"), + sourcePackageAlias("acp-core", "record-shared"), + sourcePackageAlias("acp-core", "runtime/types"), + sourcePackageAlias("acp-core"), ...sourcePluginSdkSubpaths.map((subpath) => ({ find: `openclaw/plugin-sdk/${subpath}`, replacement: path.join(repoRoot, "src", "plugin-sdk", `${subpath}.ts`), diff --git a/test/vitest/vitest.unit-fast-paths.mjs b/test/vitest/vitest.unit-fast-paths.mjs index 3d1c037da5f1..e3a38c23b3d1 100644 --- a/test/vitest/vitest.unit-fast-paths.mjs +++ b/test/vitest/vitest.unit-fast-paths.mjs @@ -32,6 +32,7 @@ const unitFastCandidateGlobs = [ "src/link-understanding/**/*.test.ts", "src/logging/**/*.test.ts", "packages/markdown-core/src/**/*.test.ts", + "packages/media-core/src/**/*.test.ts", "packages/terminal-core/src/**/*.test.ts", "src/media/**/*.test.ts", "src/media-generation/**/*.test.ts", diff --git a/tsconfig.json b/tsconfig.json index 06e00e44cbb7..90d289e7dd03 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -88,6 +88,28 @@ "./packages/media-generation-core/src/normalization.ts" ], "@openclaw/media-generation-core/*": ["./packages/media-generation-core/src/*"], + "@openclaw/media-core": ["./packages/media-core/src/index.ts"], + "@openclaw/media-core/base64": ["./packages/media-core/src/base64.ts"], + "@openclaw/media-core/constants": ["./packages/media-core/src/constants.ts"], + "@openclaw/media-core/content-length": ["./packages/media-core/src/content-length.ts"], + "@openclaw/media-core/file-name": ["./packages/media-core/src/file-name.ts"], + "@openclaw/media-core/inbound-path-policy": [ + "./packages/media-core/src/inbound-path-policy.ts" + ], + "@openclaw/media-core/inline-image-data-url": [ + "./packages/media-core/src/inline-image-data-url.ts" + ], + "@openclaw/media-core/media-source-url": [ + "./packages/media-core/src/media-source-url.ts" + ], + "@openclaw/media-core/mime": ["./packages/media-core/src/mime.ts"], + "@openclaw/media-core/read-byte-stream-with-limit": [ + "./packages/media-core/src/read-byte-stream-with-limit.ts" + ], + "@openclaw/media-core/read-response-with-limit": [ + "./packages/media-core/src/read-response-with-limit.ts" + ], + "@openclaw/media-core/*": ["./packages/media-core/src/*"], "@openclaw/media-understanding-common": [ "./packages/media-understanding-common/src/index.ts" ], @@ -120,6 +142,11 @@ "./packages/normalization-core/src/string-normalization.ts" ], "@openclaw/normalization-core/*": ["./packages/normalization-core/src/*"], + "@openclaw/acp-core": ["./packages/acp-core/src/index.ts"], + "@openclaw/acp-core/normalize-text": ["./packages/acp-core/src/normalize-text.ts"], + "@openclaw/acp-core/record-shared": ["./packages/acp-core/src/record-shared.ts"], + "@openclaw/acp-core/runtime/types": ["./packages/acp-core/src/runtime/types.ts"], + "@openclaw/acp-core/*": ["./packages/acp-core/src/*"], "@openclaw/terminal-core": ["./packages/terminal-core/src/index.ts"], "@openclaw/terminal-core/ansi": ["./packages/terminal-core/src/ansi.ts"], "@openclaw/terminal-core/decorative-emoji": [ diff --git a/tsconfig.plugin-sdk.dts.json b/tsconfig.plugin-sdk.dts.json index 3c87bf536a92..1c004b6887ec 100644 --- a/tsconfig.plugin-sdk.dts.json +++ b/tsconfig.plugin-sdk.dts.json @@ -15,10 +15,12 @@ "src/plugin-sdk/**/*.ts", "packages/llm-core/src/**/*.ts", "packages/markdown-core/src/**/*.ts", + "packages/media-core/src/**/*.ts", "packages/media-generation-core/src/**/*.ts", "packages/model-catalog-core/src/**/*.ts", "packages/memory-host-sdk/src/**/*.ts", "packages/normalization-core/src/**/*.ts", + "packages/acp-core/src/**/*.ts", "packages/terminal-core/src/**/*.ts", "src/video-generation/dashscope-compatible.ts", "src/video-generation/types.ts", diff --git a/tsdown.config.ts b/tsdown.config.ts index e72c76652c45..781756a9b143 100644 --- a/tsdown.config.ts +++ b/tsdown.config.ts @@ -217,6 +217,10 @@ function shouldAlwaysBundleDependency(id: string): boolean { id.startsWith("@openclaw/fs-safe/") || id === "@openclaw/normalization-core" || id.startsWith("@openclaw/normalization-core/") || + id === "@openclaw/media-core" || + id.startsWith("@openclaw/media-core/") || + id === "@openclaw/acp-core" || + id.startsWith("@openclaw/acp-core/") || id === "zod" || id.startsWith("zod/") ); @@ -437,6 +441,31 @@ function buildNormalizationCoreDistEntries(): Record { }; } +function buildMediaCoreDistEntries(): Record { + return { + index: "packages/media-core/src/index.ts", + base64: "packages/media-core/src/base64.ts", + constants: "packages/media-core/src/constants.ts", + "content-length": "packages/media-core/src/content-length.ts", + "file-name": "packages/media-core/src/file-name.ts", + "inbound-path-policy": "packages/media-core/src/inbound-path-policy.ts", + "inline-image-data-url": "packages/media-core/src/inline-image-data-url.ts", + "media-source-url": "packages/media-core/src/media-source-url.ts", + mime: "packages/media-core/src/mime.ts", + "read-byte-stream-with-limit": "packages/media-core/src/read-byte-stream-with-limit.ts", + "read-response-with-limit": "packages/media-core/src/read-response-with-limit.ts", + }; +} + +function buildAcpCoreDistEntries(): Record { + return { + index: "packages/acp-core/src/index.ts", + "normalize-text": "packages/acp-core/src/normalize-text.ts", + "record-shared": "packages/acp-core/src/record-shared.ts", + "runtime/types": "packages/acp-core/src/runtime/types.ts", + }; +} + function buildTerminalCoreDistEntries(): Record { return { index: "packages/terminal-core/src/index.ts", @@ -577,6 +606,18 @@ function buildUnifiedDistEntries(): Record { source, ]), ), + ...Object.fromEntries( + Object.entries(buildMediaCoreDistEntries()).map(([entry, source]) => [ + `media-core/${entry}`, + source, + ]), + ), + ...Object.fromEntries( + Object.entries(buildAcpCoreDistEntries()).map(([entry, source]) => [ + `acp-core/${entry}`, + source, + ]), + ), ...Object.fromEntries( Object.entries(buildTerminalCoreDistEntries()).map(([entry, source]) => [ `terminal-core/${entry}`, @@ -669,6 +710,18 @@ export default defineConfig([ entry: buildNormalizationCoreDistEntries(), outDir: tsdownPackageOutputRoot("normalization-core"), }), + nodeWorkspacePackageBuildConfig({ + clean: true, + dts: RUN_NODE_SKIP_DTS_BUILD ? false : undefined, + entry: buildMediaCoreDistEntries(), + outDir: tsdownPackageOutputRoot("media-core"), + }), + nodeWorkspacePackageBuildConfig({ + clean: true, + dts: RUN_NODE_SKIP_DTS_BUILD ? false : undefined, + entry: buildAcpCoreDistEntries(), + outDir: tsdownPackageOutputRoot("acp-core"), + }), nodeWorkspacePackageBuildConfig({ clean: true, dts: RUN_NODE_SKIP_DTS_BUILD ? false : undefined, diff --git a/ui/package.json b/ui/package.json index 7be41f461794..87a0f751bda5 100644 --- a/ui/package.json +++ b/ui/package.json @@ -11,6 +11,7 @@ "dependencies": { "@create-markdown/preview": "2.0.3", "@noble/ed25519": "3.1.0", + "@openclaw/media-core": "workspace:*", "@openclaw/normalization-core": "workspace:*", "dompurify": "3.4.7", "highlight.js": "11.11.1", diff --git a/ui/src/ui/chat/message-normalizer.ts b/ui/src/ui/chat/message-normalizer.ts index 50447e6fc0dd..10650de3db39 100644 --- a/ui/src/ui/chat/message-normalizer.ts +++ b/ui/src/ui/chat/message-normalizer.ts @@ -2,6 +2,7 @@ * Message normalization utilities for chat rendering. */ +import { mediaKindFromMime } from "@openclaw/media-core/constants"; import { stripInboundMetadata } from "../../../../src/auto-reply/reply/strip-inbound-meta.js"; import { extractCanvasShortcodes } from "../../../../src/chat/canvas-render.js"; import { @@ -9,7 +10,6 @@ import { isToolResultContentType, resolveToolBlockArgs, } from "../../../../src/chat/tool-content.js"; -import { mediaKindFromMime } from "../../../../src/media/constants.js"; import { splitMediaFromOutput } from "../../../../src/media/parse.js"; import { parseInlineDirectives } from "../../../../src/utils/directive-tags.js"; import type { NormalizedMessage, MessageContentItem } from "../types/chat-types.ts"; diff --git a/ui/vitest.config.ts b/ui/vitest.config.ts index dc3ff658314a..3ff6dd9a69b8 100644 --- a/ui/vitest.config.ts +++ b/ui/vitest.config.ts @@ -25,6 +25,10 @@ export default defineConfig({ find: /^@openclaw\/normalization-core\/(.+)$/u, replacement: path.resolve(repoRoot, "packages/normalization-core/src/$1"), }, + { + find: /^@openclaw\/media-core\/(.+)$/u, + replacement: path.resolve(repoRoot, "packages/media-core/src/$1"), + }, ], }, test: {