mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
docs: document canvas plugin
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
/**
|
||||
* Canvas CLI metadata entrypoint used for lightweight command discovery.
|
||||
*/
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
||||
|
||||
export default definePluginEntry({
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* Canvas plugin entrypoint for node canvas control, hosted A2UI routes, and
|
||||
* node CLI registration.
|
||||
*/
|
||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
import type { Duplex } from "node:stream";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* Runtime API exports for Canvas plugin host, document, CLI, and capability
|
||||
* helpers.
|
||||
*/
|
||||
export {
|
||||
canvasConfigSchema,
|
||||
isCanvasHostEnabled,
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Bundles the Canvas A2UI web app and writes a hash for tracked inputs.
|
||||
*/
|
||||
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { createHash } from "node:crypto";
|
||||
@@ -42,10 +45,12 @@ function normalizePath(filePath) {
|
||||
return filePath.split(path.sep).join("/");
|
||||
}
|
||||
|
||||
/** Returns whether a path should participate in the A2UI bundle input hash. */
|
||||
export function isBundleHashInputPath(filePath, repoRoot = rootDir) {
|
||||
return Boolean(filePath && repoRoot);
|
||||
}
|
||||
|
||||
/** Returns local Rolldown CLI candidates for the current install layout. */
|
||||
export function getLocalRolldownCliCandidates(repoRoot = rootDir) {
|
||||
return [
|
||||
path.join(repoRoot, "node_modules", "rolldown", "bin", "cli.mjs"),
|
||||
@@ -63,6 +68,7 @@ export function getLocalRolldownCliCandidates(repoRoot = rootDir) {
|
||||
];
|
||||
}
|
||||
|
||||
/** Returns repository paths that define the A2UI bundle hash inputs. */
|
||||
export function getBundleHashRepoInputPaths(repoRoot = rootDir) {
|
||||
return [
|
||||
path.join(repoRoot, "package.json"),
|
||||
@@ -71,10 +77,12 @@ export function getBundleHashRepoInputPaths(repoRoot = rootDir) {
|
||||
];
|
||||
}
|
||||
|
||||
/** Returns A2UI bundle hash input paths. */
|
||||
export function getBundleHashInputPaths(repoRoot = rootDir) {
|
||||
return getBundleHashRepoInputPaths(repoRoot);
|
||||
}
|
||||
|
||||
/** Compares paths after normalizing separators to POSIX slashes. */
|
||||
export function compareNormalizedPaths(left, right) {
|
||||
const normalizedLeft = normalizePath(left);
|
||||
const normalizedRight = normalizePath(right);
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Copies bundled Canvas A2UI assets into the dist host asset directory.
|
||||
*/
|
||||
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
@@ -17,6 +20,7 @@ function shouldSkipMissingA2uiAssets(env = process.env) {
|
||||
return env.OPENCLAW_A2UI_SKIP_MISSING === "1" || Boolean(env.OPENCLAW_SPARSE_PROFILE);
|
||||
}
|
||||
|
||||
/** Copies A2UI assets, optionally tolerating missing bundles in sparse builds. */
|
||||
export async function copyA2uiAssets({ srcDir, outDir }) {
|
||||
const skipMissing = shouldSkipMissingA2uiAssets(process.env);
|
||||
try {
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
/**
|
||||
* Cross-platform pnpm command resolver used by Canvas build scripts.
|
||||
*/
|
||||
import { accessSync, closeSync, constants, openSync, readSync, statSync } from "node:fs";
|
||||
|
||||
const WINDOWS_UNSAFE_CMD_CHARS_RE = /[&|<>%\r\n]/;
|
||||
@@ -117,6 +120,7 @@ function resolveConfiguredPnpmExec(params) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** Resolves a safe pnpm command spec for Unix, Windows, and npm_execpath launches. */
|
||||
export function resolvePnpmRunner(params = {}) {
|
||||
const configured = resolveConfiguredPnpmExec(params);
|
||||
if (configured) {
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
/**
|
||||
* Canvas setup entrypoint that exposes config migrations.
|
||||
*/
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { migrateLegacyCanvasHostConfig } from "./src/config-migration.js";
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
/**
|
||||
* A2UI JSONL helpers for Canvas text rendering and validation.
|
||||
*/
|
||||
const A2UI_ACTION_KEYS = [
|
||||
"beginRendering",
|
||||
"surfaceUpdate",
|
||||
@@ -6,8 +9,10 @@ const A2UI_ACTION_KEYS = [
|
||||
"createSurface",
|
||||
] as const;
|
||||
|
||||
/** Supported A2UI message dialects accepted by the Canvas host. */
|
||||
export type A2UIVersion = "v0.8" | "v0.9";
|
||||
|
||||
/** Builds a minimal A2UI JSONL payload that renders text in a single surface. */
|
||||
export function buildA2UITextJsonl(text: string) {
|
||||
const surfaceId = "main";
|
||||
const rootId = "root";
|
||||
@@ -35,6 +40,7 @@ export function buildA2UITextJsonl(text: string) {
|
||||
return payloads.map((payload) => JSON.stringify(payload)).join("\n");
|
||||
}
|
||||
|
||||
/** Validates A2UI JSONL and returns the detected dialect/version metadata. */
|
||||
export function validateA2UIJsonl(jsonl: string) {
|
||||
const lines = jsonl.split(/\r?\n/);
|
||||
const errors: string[] = [];
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
/**
|
||||
* Canvas capability-token helpers for scoped hosted node URLs.
|
||||
*/
|
||||
import {
|
||||
buildPluginNodeCapabilityScopedHostUrl,
|
||||
DEFAULT_PLUGIN_NODE_CAPABILITY_TTL_MS,
|
||||
@@ -7,19 +10,25 @@ import {
|
||||
type NormalizedPluginNodeCapabilityUrl,
|
||||
} from "openclaw/plugin-sdk/gateway-runtime";
|
||||
|
||||
/** Path prefix used for Canvas capability-scoped gateway routes. */
|
||||
export const CANVAS_CAPABILITY_PATH_PREFIX = PLUGIN_NODE_CAPABILITY_PATH_PREFIX;
|
||||
/** Default Canvas capability token TTL in milliseconds. */
|
||||
export const CANVAS_CAPABILITY_TTL_MS = DEFAULT_PLUGIN_NODE_CAPABILITY_TTL_MS;
|
||||
|
||||
/** Normalized Canvas capability-scoped URL shape. */
|
||||
export type NormalizedCanvasScopedUrl = NormalizedPluginNodeCapabilityUrl;
|
||||
|
||||
/** Creates a new opaque Canvas capability token. */
|
||||
export function mintCanvasCapabilityToken(): string {
|
||||
return mintPluginNodeCapabilityToken();
|
||||
}
|
||||
|
||||
/** Builds a Canvas host URL scoped by the supplied capability token. */
|
||||
export function buildCanvasScopedHostUrl(baseUrl: string, capability: string): string | undefined {
|
||||
return buildPluginNodeCapabilityScopedHostUrl(baseUrl, capability);
|
||||
}
|
||||
|
||||
/** Normalizes and validates a Canvas capability-scoped URL. */
|
||||
export function normalizeCanvasScopedUrl(rawUrl: string): NormalizedCanvasScopedUrl {
|
||||
return normalizePluginNodeCapabilityScopedUrl(rawUrl);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
/**
|
||||
* Shared Canvas CLI helpers for snapshot payload parsing and temp paths.
|
||||
*/
|
||||
import { randomUUID } from "node:crypto";
|
||||
import fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
@@ -20,6 +23,7 @@ function normalizeCanvasSnapshotFormat(value: string | undefined): CanvasSnapsho
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Normalizes Canvas snapshot output extensions, mapping jpeg to jpg. */
|
||||
export function normalizeCanvasSnapshotFileExtension(value: string): CanvasSnapshotFileExtension {
|
||||
const format = normalizeCanvasSnapshotFormat(value.startsWith(".") ? value.slice(1) : value);
|
||||
if (!format) {
|
||||
@@ -28,6 +32,7 @@ export function normalizeCanvasSnapshotFileExtension(value: string): CanvasSnaps
|
||||
return format === "jpeg" ? "jpg" : format;
|
||||
}
|
||||
|
||||
/** Parses the node.invoke canvas.snapshot payload shape. */
|
||||
export function parseCanvasSnapshotPayload(value: unknown): CanvasSnapshotPayload {
|
||||
const obj = asRecord(value);
|
||||
const format = normalizeCanvasSnapshotFormat(readStringValue(obj.format));
|
||||
@@ -61,6 +66,7 @@ function resolveTempPathParts(opts: { ext: string; tmpDir?: string; id?: string
|
||||
};
|
||||
}
|
||||
|
||||
/** Builds a safe temp path for a Canvas snapshot output file. */
|
||||
export function canvasSnapshotTempPath(opts: { ext: string; tmpDir?: string; id?: string }) {
|
||||
const { tmpDir, id, ext } = resolveTempPathParts(opts);
|
||||
const cliName = resolveCliName();
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
/**
|
||||
* Canvas node CLI command registration and runtime dependency wiring.
|
||||
*/
|
||||
import { randomUUID } from "node:crypto";
|
||||
import fs from "node:fs/promises";
|
||||
import type { Command } from "commander";
|
||||
@@ -21,6 +24,7 @@ import { shortenHomePath } from "openclaw/plugin-sdk/text-utility-runtime";
|
||||
import { buildA2UITextJsonl, validateA2UIJsonl } from "./a2ui-jsonl.js";
|
||||
import { canvasSnapshotTempPath, parseCanvasSnapshotPayload } from "./cli-helpers.js";
|
||||
|
||||
/** Runtime output surface used by Canvas CLI commands. */
|
||||
export type CanvasCliRuntime = {
|
||||
log: (message: string) => void;
|
||||
error: (message: string) => void;
|
||||
@@ -28,6 +32,7 @@ export type CanvasCliRuntime = {
|
||||
writeJson: (value: unknown) => void;
|
||||
};
|
||||
|
||||
/** Parent node/gateway options consumed by Canvas CLI commands. */
|
||||
export type CanvasNodesRpcOpts = {
|
||||
url?: string;
|
||||
token?: string;
|
||||
@@ -48,6 +53,7 @@ export type CanvasNodesRpcOpts = {
|
||||
quality?: string;
|
||||
};
|
||||
|
||||
/** Dependency bundle used to keep Canvas CLI commands testable. */
|
||||
export type CanvasCliDependencies = {
|
||||
defaultRuntime: CanvasCliRuntime;
|
||||
nodesCallOpts: (cmd: Command, defaults?: { timeoutMs?: number }) => Command;
|
||||
@@ -173,6 +179,7 @@ function unauthorizedHintForMessage(message: string): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Creates the default Canvas CLI dependency bundle backed by the OpenClaw gateway CLI. */
|
||||
export function createDefaultCanvasCliDependencies(): CanvasCliDependencies {
|
||||
const nodesCallOpts = (cmd: Command, defaults?: { timeoutMs?: number }) =>
|
||||
cmd
|
||||
@@ -252,6 +259,7 @@ async function invokeCanvas(
|
||||
);
|
||||
}
|
||||
|
||||
/** Registers Canvas subcommands under the nodes CLI command group. */
|
||||
export function registerNodesCanvasCommands(nodes: Command, deps: CanvasCliDependencies) {
|
||||
const canvas = nodes
|
||||
.command("canvas")
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
/**
|
||||
* Canvas config migration from legacy root canvasHost config to plugin config.
|
||||
*/
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { asOptionalRecord as readRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
@@ -10,6 +13,7 @@ function mergeHostConfig(params: {
|
||||
return Object.assign({}, params.legacyHost, params.existingHost);
|
||||
}
|
||||
|
||||
/** Migrates root canvasHost config into plugins.entries.canvas.config.host. */
|
||||
export function migrateLegacyCanvasHostConfig(config: OpenClawConfig): {
|
||||
config: OpenClawConfig;
|
||||
changes: string[];
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
/**
|
||||
* Canvas plugin config parsing, enablement, and schema metadata.
|
||||
*/
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import {
|
||||
normalizePluginsConfig,
|
||||
@@ -11,6 +14,7 @@ import {
|
||||
readStringValue as readString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
/** Host-server configuration for Canvas and A2UI assets. */
|
||||
export type CanvasHostConfig = {
|
||||
enabled?: boolean;
|
||||
root?: string;
|
||||
@@ -18,6 +22,7 @@ export type CanvasHostConfig = {
|
||||
liveReload?: boolean;
|
||||
};
|
||||
|
||||
/** Canvas plugin configuration shape. */
|
||||
export type CanvasPluginConfig = {
|
||||
host?: CanvasHostConfig;
|
||||
};
|
||||
@@ -47,6 +52,7 @@ function parseCanvasHostConfig(value: unknown): CanvasHostConfig | undefined {
|
||||
};
|
||||
}
|
||||
|
||||
/** Parses raw Canvas plugin config into a typed, normalized shape. */
|
||||
export function parseCanvasPluginConfig(value: unknown): CanvasPluginConfig {
|
||||
if (!isRecord(value)) {
|
||||
return {};
|
||||
@@ -55,6 +61,7 @@ export function parseCanvasPluginConfig(value: unknown): CanvasPluginConfig {
|
||||
return host ? { host } : {};
|
||||
}
|
||||
|
||||
/** Returns whether the bundled Canvas plugin is effectively enabled. */
|
||||
export function isCanvasPluginEnabled(config?: OpenClawConfig): boolean {
|
||||
if (!config) {
|
||||
return true;
|
||||
@@ -68,6 +75,7 @@ export function isCanvasPluginEnabled(config?: OpenClawConfig): boolean {
|
||||
}).enabled;
|
||||
}
|
||||
|
||||
/** Resolves Canvas host config from plugin config or root config. */
|
||||
export function resolveCanvasHostConfig(params: {
|
||||
config?: OpenClawConfig;
|
||||
pluginConfig?: Record<string, unknown>;
|
||||
@@ -78,6 +86,7 @@ export function resolveCanvasHostConfig(params: {
|
||||
return parsedPluginConfig.host ?? {};
|
||||
}
|
||||
|
||||
/** Returns whether the Canvas hosted route/server surface should be active. */
|
||||
export function isCanvasHostEnabled(config?: OpenClawConfig): boolean {
|
||||
if (isTruthyEnvValue(process.env.OPENCLAW_SKIP_CANVAS_HOST)) {
|
||||
return false;
|
||||
@@ -88,6 +97,7 @@ export function isCanvasHostEnabled(config?: OpenClawConfig): boolean {
|
||||
return resolveCanvasHostConfig({ config }).enabled !== false;
|
||||
}
|
||||
|
||||
/** Config schema metadata for Canvas plugin settings. */
|
||||
export const canvasConfigSchema: CanvasPluginConfigSchema = {
|
||||
parse: parseCanvasPluginConfig,
|
||||
uiHints: {
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* Canvas document materialization helpers for hosted HTML, media, documents,
|
||||
* and asset manifests.
|
||||
*/
|
||||
import { randomUUID } from "node:crypto";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
@@ -122,6 +126,7 @@ function resolveCanvasDocumentsDir(rootDir?: string, stateDir = resolveStateDir(
|
||||
return path.join(resolveCanvasRootDir(rootDir, stateDir), CANVAS_DOCUMENTS_DIR_NAME);
|
||||
}
|
||||
|
||||
/** Resolves the on-disk directory for one Canvas document id. */
|
||||
export function resolveCanvasDocumentDir(
|
||||
documentId: string,
|
||||
options?: { rootDir?: string; stateDir?: string },
|
||||
@@ -129,6 +134,7 @@ export function resolveCanvasDocumentDir(
|
||||
return path.join(resolveCanvasDocumentsDir(options?.rootDir, options?.stateDir), documentId);
|
||||
}
|
||||
|
||||
/** Builds the hosted URL path for a Canvas document entrypoint. */
|
||||
export function buildCanvasDocumentEntryUrl(documentId: string, entrypoint: string): string {
|
||||
const normalizedEntrypoint = normalizeLogicalPath(entrypoint);
|
||||
const encodedEntrypoint = normalizedEntrypoint
|
||||
@@ -142,6 +148,7 @@ function buildCanvasDocumentAssetUrl(documentId: string, logicalPath: string): s
|
||||
return buildCanvasDocumentEntryUrl(documentId, logicalPath);
|
||||
}
|
||||
|
||||
/** Maps a Canvas hosted document URL path back to a local file path. */
|
||||
export function resolveCanvasHttpPathToLocalPath(
|
||||
requestPath: string,
|
||||
options?: { rootDir?: string; stateDir?: string },
|
||||
@@ -289,6 +296,7 @@ async function materializeEntrypoint(
|
||||
};
|
||||
}
|
||||
|
||||
/** Creates a Canvas document directory, copies assets, and writes its manifest. */
|
||||
export async function createCanvasDocument(
|
||||
input: CanvasDocumentCreateInput,
|
||||
options?: { stateDir?: string; workspaceDir?: string; canvasRootDir?: string },
|
||||
@@ -322,6 +330,7 @@ export async function createCanvasDocument(
|
||||
return manifest;
|
||||
}
|
||||
|
||||
/** Resolves manifest assets to local paths and hosted URLs. */
|
||||
export function resolveCanvasDocumentAssets(
|
||||
manifest: CanvasDocumentManifest,
|
||||
options?: { baseUrl?: string; stateDir?: string; canvasRootDir?: string },
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
/**
|
||||
* Canvas hosted-surface URL resolver.
|
||||
*/
|
||||
import {
|
||||
resolveHostedPluginSurfaceUrl,
|
||||
type HostedPluginSurfaceUrlParams,
|
||||
@@ -7,6 +10,7 @@ type CanvasHostUrlParams = Omit<HostedPluginSurfaceUrlParams, "port"> & {
|
||||
canvasPort?: number;
|
||||
};
|
||||
|
||||
/** Resolves the externally visible Canvas host URL for a gateway/plugin surface. */
|
||||
export function resolveCanvasHostUrl(params: CanvasHostUrlParams) {
|
||||
return resolveHostedPluginSurfaceUrl({
|
||||
...params,
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* Canvas A2UI browser bootstrap that installs theme overrides and native bridge
|
||||
* helpers.
|
||||
*/
|
||||
import { v0_8 } from "@a2ui/lit";
|
||||
import { ContextProvider } from "@lit/context";
|
||||
import { themeContext } from "@openclaw/a2ui-theme-context";
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
/**
|
||||
* Rolldown config for bundling the Canvas A2UI app into a single browser asset.
|
||||
*/
|
||||
import { existsSync } from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import path from "node:path";
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
/**
|
||||
* Shared A2UI/Canvas host paths and live-reload injection helpers.
|
||||
*/
|
||||
import { lowercasePreservingWhitespace } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
/** Hosted path prefix for bundled A2UI assets. */
|
||||
export const A2UI_PATH = "/__openclaw__/a2ui";
|
||||
|
||||
/** Hosted path prefix for Canvas document/static assets. */
|
||||
export const CANVAS_HOST_PATH = "/__openclaw__/canvas";
|
||||
|
||||
/** Hosted WebSocket path for Canvas live reload. */
|
||||
export const CANVAS_WS_PATH = "/__openclaw__/ws";
|
||||
|
||||
/** Returns whether a URL path targets the hosted A2UI asset surface. */
|
||||
export function isA2uiPath(pathname: string): boolean {
|
||||
return pathname === A2UI_PATH || pathname.startsWith(`${A2UI_PATH}/`);
|
||||
}
|
||||
|
||||
/** Injects Canvas bridge helpers and live-reload WebSocket code into HTML. */
|
||||
export function injectCanvasLiveReload(html: string): string {
|
||||
const snippet = `
|
||||
<script>
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
/**
|
||||
* HTTP handler for serving bundled A2UI assets through Canvas host routes.
|
||||
*/
|
||||
import fs from "node:fs/promises";
|
||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
import path from "node:path";
|
||||
@@ -77,6 +80,7 @@ async function resolveA2uiRootReal(): Promise<string | null> {
|
||||
return resolvingA2uiRoot;
|
||||
}
|
||||
|
||||
/** Handles one HTTP request for the hosted A2UI asset surface. */
|
||||
export async function handleA2uiHttpRequest(
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse,
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
/**
|
||||
* Safe file resolution helpers for Canvas-hosted static assets.
|
||||
*/
|
||||
import path from "node:path";
|
||||
import { root as fsRoot, FsSafeError } from "openclaw/plugin-sdk/security-runtime";
|
||||
|
||||
type CanvasOpenResult = Awaited<ReturnType<Awaited<ReturnType<typeof fsRoot>>["open"]>>;
|
||||
|
||||
/** Normalizes a decoded URL path into a leading-slash POSIX path. */
|
||||
export function normalizeUrlPath(rawPath: string): string {
|
||||
const decoded = decodeURIComponent(rawPath || "/");
|
||||
const normalized = path.posix.normalize(decoded);
|
||||
@@ -41,6 +45,7 @@ function tryNormalizeUrlPath(rawPath: string): string | null {
|
||||
return normalized.startsWith("/") ? normalized : `/${normalized}`;
|
||||
}
|
||||
|
||||
/** Opens a Canvas-hosted file only when the request stays inside the root. */
|
||||
export async function resolveFileWithinRoot(
|
||||
rootReal: string,
|
||||
urlPath: string,
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
/**
|
||||
* Canvas host server and static-file/live-reload handler implementation.
|
||||
*/
|
||||
import * as fsSync from "node:fs";
|
||||
import fs from "node:fs/promises";
|
||||
import http, { type IncomingMessage, type Server, type ServerResponse } from "node:http";
|
||||
@@ -29,6 +32,7 @@ import { normalizeUrlPath, resolveFileWithinRoot } from "./file-resolver.js";
|
||||
|
||||
type ChokidarWatch = typeof import("chokidar").watch;
|
||||
|
||||
/** Options for Canvas host creation. */
|
||||
export type CanvasHostOpts = {
|
||||
runtime: RuntimeEnv;
|
||||
rootDir?: string;
|
||||
@@ -40,17 +44,20 @@ export type CanvasHostOpts = {
|
||||
webSocketServerClass?: typeof WebSocketServer;
|
||||
};
|
||||
|
||||
/** Options for starting a standalone Canvas host HTTP server. */
|
||||
export type CanvasHostServerOpts = CanvasHostOpts & {
|
||||
handler?: CanvasHostHandler;
|
||||
ownsHandler?: boolean;
|
||||
};
|
||||
|
||||
/** Running Canvas host server handle. */
|
||||
export type CanvasHostServer = {
|
||||
port: number;
|
||||
rootDir: string;
|
||||
close: () => Promise<void>;
|
||||
};
|
||||
|
||||
/** Options for creating only the Canvas host request handler. */
|
||||
export type CanvasHostHandlerOpts = {
|
||||
runtime: RuntimeEnv;
|
||||
rootDir?: string;
|
||||
@@ -61,6 +68,7 @@ export type CanvasHostHandlerOpts = {
|
||||
webSocketServerClass?: typeof WebSocketServer;
|
||||
};
|
||||
|
||||
/** Canvas host handler for HTTP requests, WebSocket upgrades, and teardown. */
|
||||
export type CanvasHostHandler = {
|
||||
rootDir: string;
|
||||
basePath: string;
|
||||
@@ -244,6 +252,7 @@ function resolveDefaultWatchFactory(): ChokidarWatch {
|
||||
throw new Error("chokidar.watch unavailable");
|
||||
}
|
||||
|
||||
/** Creates a Canvas static-file handler with optional live reload. */
|
||||
export async function createCanvasHostHandler(
|
||||
opts: CanvasHostHandlerOpts,
|
||||
): Promise<CanvasHostHandler> {
|
||||
@@ -451,6 +460,7 @@ export async function createCanvasHostHandler(
|
||||
};
|
||||
}
|
||||
|
||||
/** Starts a standalone loopback Canvas host HTTP server. */
|
||||
export async function startCanvasHost(opts: CanvasHostServerOpts): Promise<CanvasHostServer> {
|
||||
if (isDisabledByEnv() && opts.allowInTests !== true) {
|
||||
return { port: 0, rootDir: "", close: async () => {} };
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* Canvas HTTP route adapter that lazily starts the host handler for plugin
|
||||
* routed requests.
|
||||
*/
|
||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
import type { Duplex } from "node:stream";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
@@ -6,12 +10,14 @@ import { isCanvasHostEnabled, resolveCanvasHostConfig } from "./config.js";
|
||||
import { A2UI_PATH, CANVAS_HOST_PATH, CANVAS_WS_PATH, handleA2uiHttpRequest } from "./host/a2ui.js";
|
||||
import { createCanvasHostHandler, type CanvasHostHandler } from "./host/server.js";
|
||||
|
||||
/** Canvas route handler shape registered with the plugin HTTP router. */
|
||||
export type CanvasHttpRouteHandler = {
|
||||
handleHttpRequest: (req: IncomingMessage, res: ServerResponse) => Promise<boolean>;
|
||||
handleUpgrade: (req: IncomingMessage, socket: Duplex, head: Buffer) => Promise<boolean>;
|
||||
close: () => Promise<void>;
|
||||
};
|
||||
|
||||
/** Creates a lazily initialized Canvas HTTP/WebSocket route handler. */
|
||||
export function createCanvasHttpRouteHandler(params: {
|
||||
config: OpenClawConfig;
|
||||
pluginConfig?: Record<string, unknown>;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
/**
|
||||
* Agent-facing Canvas tool schema and allowed action/format enums.
|
||||
*/
|
||||
import {
|
||||
optionalFiniteNumberSchema,
|
||||
optionalNonNegativeIntegerSchema,
|
||||
@@ -6,6 +9,7 @@ import {
|
||||
} from "openclaw/plugin-sdk/channel-actions";
|
||||
import { Type } from "typebox";
|
||||
|
||||
/** Agent tool actions supported by the Canvas plugin. */
|
||||
export const CANVAS_ACTIONS = [
|
||||
"present",
|
||||
"hide",
|
||||
@@ -16,8 +20,10 @@ export const CANVAS_ACTIONS = [
|
||||
"a2ui_reset",
|
||||
] as const;
|
||||
|
||||
/** Snapshot formats accepted by the Canvas tool. */
|
||||
export const CANVAS_SNAPSHOT_FORMATS = ["png", "jpg", "jpeg"] as const;
|
||||
|
||||
/** TypeBox schema for the model-facing Canvas tool arguments. */
|
||||
export const CanvasToolSchema = Type.Object({
|
||||
action: stringEnum(CANVAS_ACTIONS),
|
||||
gatewayUrl: Type.Optional(Type.String()),
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* Agent-facing Canvas tool implementation for node canvas commands and
|
||||
* snapshots.
|
||||
*/
|
||||
import { randomUUID } from "node:crypto";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
@@ -85,6 +89,7 @@ function resolveCanvasImageSanitizationLimits(
|
||||
return { maxDimensionPx: Math.max(1, Math.floor(configured)) };
|
||||
}
|
||||
|
||||
/** Creates the model-facing Canvas tool used to invoke paired node canvas commands. */
|
||||
export function createCanvasTool(options?: CanvasToolOptions): AnyAgentTool {
|
||||
const imageSanitization = resolveCanvasImageSanitizationLimits(options?.config);
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user