Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
221
extensions/canvas/src/tool.ts
Normal file
221
extensions/canvas/src/tool.ts
Normal file
@@ -0,0 +1,221 @@
|
||||
/**
|
||||
* 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";
|
||||
import {
|
||||
callGatewayTool,
|
||||
listNodes,
|
||||
resolveNodeIdFromList,
|
||||
} from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import {
|
||||
imageResultFromFile,
|
||||
jsonResult,
|
||||
readStringParam,
|
||||
} from "openclaw/plugin-sdk/channel-actions";
|
||||
import { readFiniteNumberParam, readPositiveIntegerParam } from "openclaw/plugin-sdk/param-readers";
|
||||
import type { AnyAgentTool, OpenClawConfig } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
|
||||
import { normalizeCanvasSnapshotFileExtension, parseCanvasSnapshotPayload } from "./cli-helpers.js";
|
||||
import { CanvasToolSchema } from "./tool-schema.js";
|
||||
|
||||
type CanvasToolOptions = {
|
||||
config?: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
};
|
||||
|
||||
type CanvasImageSanitizationLimits = {
|
||||
maxDimensionPx?: number;
|
||||
};
|
||||
|
||||
function readGatewayCallOptions(params: Record<string, unknown>) {
|
||||
return {
|
||||
gatewayUrl: readStringParam(params, "gatewayUrl", { trim: false }),
|
||||
gatewayToken: readStringParam(params, "gatewayToken", { trim: false }),
|
||||
timeoutMs: readPositiveIntegerParam(params, "timeoutMs"),
|
||||
};
|
||||
}
|
||||
|
||||
async function resolveNodeId(
|
||||
opts: ReturnType<typeof readGatewayCallOptions>,
|
||||
query?: string,
|
||||
allowDefault = false,
|
||||
): Promise<string> {
|
||||
return resolveNodeIdFromList(await listNodes(opts), query, allowDefault);
|
||||
}
|
||||
|
||||
async function writeBase64ToTempFile(params: { base64: string; ext: string }): Promise<string> {
|
||||
const dir = resolvePreferredOpenClawTmpDir();
|
||||
await fs.mkdir(dir, { recursive: true, mode: 0o700 });
|
||||
const ext = `.${normalizeCanvasSnapshotFileExtension(params.ext)}`;
|
||||
const filePath = path.join(dir, `openclaw-canvas-snapshot-${randomUUID()}${ext}`);
|
||||
await fs.writeFile(filePath, Buffer.from(params.base64, "base64"));
|
||||
return filePath;
|
||||
}
|
||||
|
||||
function isPathInsideRoot(root: string, candidate: string): boolean {
|
||||
const relative = path.relative(root, candidate);
|
||||
return (
|
||||
relative === "" || (relative !== "" && !relative.startsWith("..") && !path.isAbsolute(relative))
|
||||
);
|
||||
}
|
||||
|
||||
async function readJsonlFromPath(jsonlPath: string, workspaceDir?: string): Promise<string> {
|
||||
const trimmed = jsonlPath.trim();
|
||||
if (!trimmed) {
|
||||
return "";
|
||||
}
|
||||
const workspaceRoot = path.resolve(workspaceDir ?? process.cwd());
|
||||
const resolved = path.resolve(workspaceRoot, trimmed);
|
||||
const [workspaceReal, resolvedReal] = await Promise.all([
|
||||
fs.realpath(workspaceRoot),
|
||||
fs.realpath(resolved),
|
||||
]);
|
||||
if (!isPathInsideRoot(workspaceReal, resolvedReal)) {
|
||||
throw new Error("jsonlPath outside workspace");
|
||||
}
|
||||
return await fs.readFile(resolvedReal, "utf8");
|
||||
}
|
||||
|
||||
function resolveCanvasImageSanitizationLimits(
|
||||
config?: OpenClawConfig,
|
||||
): CanvasImageSanitizationLimits {
|
||||
const configured = config?.agents?.defaults?.imageMaxDimensionPx;
|
||||
if (typeof configured !== "number" || !Number.isFinite(configured)) {
|
||||
return {};
|
||||
}
|
||||
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 {
|
||||
label: "Canvas",
|
||||
name: "canvas",
|
||||
description:
|
||||
"Control node canvases (present/hide/navigate/eval/snapshot/A2UI). Use snapshot to capture the rendered UI.",
|
||||
parameters: CanvasToolSchema,
|
||||
execute: async (_toolCallId, args) => {
|
||||
const params = args as Record<string, unknown>;
|
||||
const action = readStringParam(params, "action", { required: true });
|
||||
const gatewayOpts = readGatewayCallOptions(params);
|
||||
|
||||
const nodeId = await resolveNodeId(
|
||||
gatewayOpts,
|
||||
readStringParam(params, "node", { trim: true }),
|
||||
true,
|
||||
);
|
||||
|
||||
const invoke = async (command: string, invokeParams?: Record<string, unknown>) =>
|
||||
await callGatewayTool("node.invoke", gatewayOpts, {
|
||||
nodeId,
|
||||
command,
|
||||
params: invokeParams,
|
||||
idempotencyKey: randomUUID(),
|
||||
});
|
||||
|
||||
switch (action) {
|
||||
case "present": {
|
||||
const placement = {
|
||||
x: readFiniteNumberParam(params, "x"),
|
||||
y: readFiniteNumberParam(params, "y"),
|
||||
width: readFiniteNumberParam(params, "width"),
|
||||
height: readFiniteNumberParam(params, "height"),
|
||||
};
|
||||
const invokeParams: Record<string, unknown> = {};
|
||||
const presentTarget =
|
||||
readStringParam(params, "target", { trim: true }) ??
|
||||
readStringParam(params, "url", { trim: true });
|
||||
if (presentTarget) {
|
||||
invokeParams.url = presentTarget;
|
||||
}
|
||||
if (
|
||||
Number.isFinite(placement.x) ||
|
||||
Number.isFinite(placement.y) ||
|
||||
Number.isFinite(placement.width) ||
|
||||
Number.isFinite(placement.height)
|
||||
) {
|
||||
invokeParams.placement = placement;
|
||||
}
|
||||
await invoke("canvas.present", invokeParams);
|
||||
return jsonResult({ ok: true });
|
||||
}
|
||||
case "hide":
|
||||
await invoke("canvas.hide", undefined);
|
||||
return jsonResult({ ok: true });
|
||||
case "navigate": {
|
||||
const url =
|
||||
readStringParam(params, "url", { trim: true }) ??
|
||||
readStringParam(params, "target", { required: true, trim: true, label: "url" });
|
||||
await invoke("canvas.navigate", { url });
|
||||
return jsonResult({ ok: true });
|
||||
}
|
||||
case "eval": {
|
||||
const javaScript = readStringParam(params, "javaScript", {
|
||||
required: true,
|
||||
});
|
||||
const raw = (await invoke("canvas.eval", { javaScript })) as {
|
||||
payload?: { result?: string };
|
||||
};
|
||||
const result = raw?.payload?.result;
|
||||
if (result) {
|
||||
return {
|
||||
content: [{ type: "text", text: result }],
|
||||
details: { result },
|
||||
};
|
||||
}
|
||||
return jsonResult({ ok: true });
|
||||
}
|
||||
case "snapshot": {
|
||||
const formatRaw =
|
||||
typeof params.outputFormat === "string" && params.outputFormat.trim()
|
||||
? params.outputFormat.trim().toLowerCase()
|
||||
: "png";
|
||||
const format = formatRaw === "jpg" || formatRaw === "jpeg" ? "jpeg" : "png";
|
||||
const maxWidth = readPositiveIntegerParam(params, "maxWidth");
|
||||
const quality = readFiniteNumberParam(params, "quality", {
|
||||
min: 0,
|
||||
max: 1,
|
||||
});
|
||||
const raw = (await invoke("canvas.snapshot", {
|
||||
format,
|
||||
maxWidth,
|
||||
quality,
|
||||
})) as { payload?: unknown };
|
||||
const payload = parseCanvasSnapshotPayload(raw?.payload);
|
||||
const filePath = await writeBase64ToTempFile({
|
||||
base64: payload.base64,
|
||||
ext: payload.format === "jpeg" ? "jpg" : payload.format,
|
||||
});
|
||||
return await imageResultFromFile({
|
||||
label: "canvas:snapshot",
|
||||
path: filePath,
|
||||
details: { format: payload.format },
|
||||
imageSanitization,
|
||||
});
|
||||
}
|
||||
case "a2ui_push": {
|
||||
const jsonl =
|
||||
typeof params.jsonl === "string" && params.jsonl.trim()
|
||||
? params.jsonl
|
||||
: typeof params.jsonlPath === "string" && params.jsonlPath.trim()
|
||||
? await readJsonlFromPath(params.jsonlPath, options?.workspaceDir)
|
||||
: "";
|
||||
if (!jsonl.trim()) {
|
||||
throw new Error("jsonl or jsonlPath required");
|
||||
}
|
||||
await invoke("canvas.a2ui.pushJSONL", { jsonl });
|
||||
return jsonResult({ ok: true });
|
||||
}
|
||||
case "a2ui_reset":
|
||||
await invoke("canvas.a2ui.reset", undefined);
|
||||
return jsonResult({ ok: true });
|
||||
default:
|
||||
throw new Error(`Unknown action: ${action}`);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user