From 634bcf6667558c475ed46c018ccf6ddf211bf206 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Tue, 9 Jun 2026 14:56:17 +0900 Subject: [PATCH] docs: clarify external app integration path --- docs/.i18n/glossary.zh-CN.json | 8 - docs/concepts/openclaw-sdk.md | 323 ------------------ docs/docs.json | 3 +- docs/gateway/external-apps.md | 86 +++++ docs/maturity-scores.yaml | 6 +- docs/plugins/sdk-overview.md | 5 +- docs/reference/openclaw-sdk-api-design.md | 390 ---------------------- 7 files changed, 92 insertions(+), 729 deletions(-) delete mode 100644 docs/concepts/openclaw-sdk.md create mode 100644 docs/gateway/external-apps.md delete mode 100644 docs/reference/openclaw-sdk-api-design.md diff --git a/docs/.i18n/glossary.zh-CN.json b/docs/.i18n/glossary.zh-CN.json index c03b1b9358f7..32127016fead 100644 --- a/docs/.i18n/glossary.zh-CN.json +++ b/docs/.i18n/glossary.zh-CN.json @@ -55,14 +55,6 @@ "source": "Mantis", "target": "Mantis" }, - { - "source": "OpenClaw App SDK", - "target": "OpenClaw 应用 SDK" - }, - { - "source": "OpenClaw App SDK API design", - "target": "OpenClaw 应用 SDK API 设计" - }, { "source": "Message lifecycle refactor", "target": "消息生命周期重构" diff --git a/docs/concepts/openclaw-sdk.md b/docs/concepts/openclaw-sdk.md deleted file mode 100644 index a41ad277a048..000000000000 --- a/docs/concepts/openclaw-sdk.md +++ /dev/null @@ -1,323 +0,0 @@ ---- -summary: "Public OpenClaw App SDK for external apps, scripts, dashboards, CI jobs, and IDE extensions" -title: "OpenClaw App SDK" -sidebarTitle: "App SDK" -read_when: - - You are building an external app, script, dashboard, CI job, or IDE extension that talks to OpenClaw - - You are choosing between the App SDK and the Plugin SDK - - You are integrating with Gateway agent runs, sessions, events, approvals, models, or tools ---- - -The **OpenClaw App SDK** is the public client API for apps outside the -OpenClaw process. Use `@openclaw/sdk` when a script, dashboard, CI job, IDE -extension, or other external app wants to connect to the Gateway, start agent -runs, stream events, wait for results, cancel work, or inspect Gateway -resources. - - - The App SDK is different from the [Plugin SDK](/plugins/sdk-overview). - `@openclaw/sdk` talks to the Gateway from outside OpenClaw. - `openclaw/plugin-sdk/*` is only for plugins that run inside OpenClaw and - register providers, channels, tools, hooks, or trusted runtimes. - - -## What ships today - -`@openclaw/sdk` ships with: - -| Surface | Status | What it does | -| ------------------------- | ------- | --------------------------------------------------------------------------------- | -| `OpenClaw` | Ready | Main client entry point. Owns transport, connection, requests, and events. | -| `GatewayClientTransport` | Ready | WebSocket transport backed by the Gateway client. | -| `oc.agents` | Ready | Lists, creates, updates, deletes, and gets agent handles. | -| `Agent.run()` | Ready | Starts a Gateway `agent` run and returns a `Run`. | -| `oc.runs` | Ready | Creates, gets, waits for, cancels, and streams runs. | -| `Run.events()` | Ready | Streams normalized per-run events with replay for fast runs. | -| `Run.wait()` | Ready | Calls `agent.wait` and returns a stable `RunResult`. | -| `Run.cancel()` | Ready | Calls `sessions.abort` by run id, with session key when available. | -| `oc.sessions` | Ready | Creates, resolves, sends to, patches, compacts, and gets session handles. | -| `Session.send()` | Ready | Calls `sessions.send` and returns a `Run`. | -| `oc.tasks` | Ready | Lists, reads, and cancels Gateway task ledger entries. | -| `oc.models` | Ready | Calls `models.list` and the current `models.authStatus` status RPC. | -| `oc.tools` | Ready | Lists, scopes, and invokes Gateway tools through the policy pipeline. | -| `oc.artifacts` | Ready | Lists, gets, and downloads Gateway transcript artifacts. | -| `oc.approvals` | Ready | Lists and resolves exec approvals through Gateway approval RPCs. | -| `oc.environments` | Partial | Lists Gateway-local and node environment candidates; create/delete are not wired. | -| `oc.rawEvents()` | Ready | Exposes raw Gateway events for advanced consumers. | -| `normalizeGatewayEvent()` | Ready | Converts raw Gateway events into the stable SDK event shape. | - -The SDK also exports the core types used by those surfaces: -`AgentRunParams`, `RunResult`, `RunStatus`, `OpenClawEvent`, -`OpenClawEventType`, `GatewayEvent`, `OpenClawTransport`, -`GatewayRequestOptions`, `SessionCreateParams`, `SessionSendParams`, -`ArtifactSummary`, `ArtifactQuery`, `ArtifactsListResult`, -`ArtifactsGetResult`, `ArtifactsDownloadResult`, -`TaskSummary`, `TaskStatus`, `TasksListParams`, `TasksListResult`, -`TasksGetResult`, `TasksCancelResult`, `RuntimeSelection`, -`EnvironmentSelection`, `WorkspaceSelection`, `ApprovalMode`, and related -result types. - -## Connect to a Gateway - -Create a client with an explicit Gateway URL, or inject a custom transport for -tests and embedded app runtimes. - -```typescript -import { OpenClaw } from "@openclaw/sdk"; - -const oc = new OpenClaw({ - url: "ws://127.0.0.1:18789", - token: process.env.OPENCLAW_GATEWAY_TOKEN, - requestTimeoutMs: 30_000, -}); - -await oc.connect(); -``` - -`new OpenClaw({ gateway: "ws://..." })` is equivalent to `url`. The -`gateway: "auto"` option is accepted by the constructor, but automatic Gateway -discovery is not a separate SDK feature yet; pass `url` when the app does not -already know how to discover the Gateway. - -For tests, pass an object that implements `OpenClawTransport`: - -```typescript -const oc = new OpenClaw({ - transport: { - async request(method, params) { - return { method, params }; - }, - async *events() {}, - }, -}); -``` - -## Run an agent - -Use `oc.agents.get(id)` when the app wants an agent handle, then call -`agent.run()`. - -```typescript -const agent = await oc.agents.get("main"); - -const run = await agent.run({ - input: "Review this pull request and suggest the smallest safe fix.", - model: "openai/gpt-5.5", - sessionKey: "main", - timeoutMs: 30_000, -}); - -for await (const event of run.events()) { - const data = event.data as { delta?: unknown }; - if (event.type === "assistant.delta" && typeof data.delta === "string") { - process.stdout.write(data.delta); - } -} - -const result = await run.wait({ timeoutMs: 120_000 }); -console.log(result.status); -``` - -Provider-qualified model refs such as `openai/gpt-5.5` are split into Gateway -`provider` and `model` overrides. `timeoutMs` stays milliseconds in the SDK and -is converted to Gateway timeout seconds for the `agent` RPC. - -`run.wait()` uses the Gateway `agent.wait` RPC. A wait deadline that expires -while the run is still active returns `status: "accepted"` instead of pretending -the run itself timed out. Runtime timeouts, aborted runs, and cancelled runs are -normalized into `timed_out` or `cancelled`. - -## Create and reuse sessions - -Use sessions when the app wants durable transcript state. - -```typescript -const session = await oc.sessions.create({ - agentId: "main", - label: "release-review", -}); - -const run = await session.send("Prepare release notes from the current diff."); -await run.wait(); -``` - -`Session.send()` calls `sessions.send` and returns a `Run`. Session handles also -support: - -```typescript -await session.abort(run.id); -await session.patch({ label: "renamed-session" }); -await session.compact({ maxLines: 200 }); -``` - -## Stream events - -The SDK normalizes raw Gateway events into a stable `OpenClawEvent` envelope: - -```typescript -type OpenClawEvent = { - version: 1; - id: string; - ts: number; - type: OpenClawEventType; - runId?: string; - sessionId?: string; - sessionKey?: string; - taskId?: string; - agentId?: string; - data: unknown; - raw?: GatewayEvent; -}; -``` - -Common event types include: - -| Event type | Source Gateway event | -| --------------------- | ------------------------------------------- | -| `run.started` | `agent` lifecycle start | -| `run.completed` | `agent` lifecycle end | -| `run.failed` | `agent` lifecycle error | -| `run.cancelled` | Aborted/cancelled lifecycle end | -| `run.timed_out` | Timeout lifecycle end | -| `assistant.delta` | Assistant streaming delta | -| `assistant.message` | Assistant message | -| `thinking.delta` | Thinking or plan stream | -| `tool.call.started` | Tool/item/command start | -| `tool.call.delta` | Tool/item/command update | -| `tool.call.completed` | Tool/item/command completion | -| `tool.call.failed` | Tool/item/command failure or blocked status | -| `approval.requested` | Exec or plugin approval request | -| `approval.resolved` | Exec or plugin approval resolution | -| `session.created` | `sessions.changed` create | -| `session.updated` | `sessions.changed` update | -| `session.compacted` | `sessions.changed` compaction | -| `task.updated` | Task update events | -| `artifact.updated` | Patch stream events | -| `raw` | Any event without a stable SDK mapping yet | - -`Run.events()` filters events to one run id and replays already-seen events for -fast runs. That means the documented flow is safe: - -```typescript -const run = await agent.run("Summarize the latest session."); - -for await (const event of run.events()) { - if (event.type === "run.completed") { - break; - } -} -``` - -For app-wide streams, use `oc.events()`. For raw Gateway frames, use -`oc.rawEvents()`. - -## Models, tools, artifacts, and approvals - -Model helpers map to current Gateway methods: - -```typescript -await oc.models.list(); -await oc.models.status({ probe: false }); // calls models.authStatus -``` - -Tool helpers expose the Gateway catalog, effective tool view, and direct -Gateway tool invocation. `oc.tools.invoke()` returns a typed envelope instead -of throwing for policy or approval refusals. - -```typescript -await oc.tools.list(); -await oc.tools.effective({ sessionKey: "main" }); -await oc.tools.invoke("tool-name", { - args: { input: "value" }, - sessionKey: "main", - confirm: false, - idempotencyKey: "tool-call-1", -}); -``` - -Artifact helpers expose the Gateway artifact projection for session, run, or -task context. Each call requires one explicit `sessionKey`, `runId`, or -`taskId` scope: - -```typescript -const { artifacts } = await oc.artifacts.list({ sessionKey: "main" }); -const first = artifacts[0]; - -if (first) { - const { artifact } = await oc.artifacts.get(first.id, { sessionKey: "main" }); - const download = await oc.artifacts.download(artifact.id, { sessionKey: "main" }); - console.log(download.encoding, download.url); -} -``` - -Approval helpers use the exec approval RPCs: - -```typescript -const approvals = await oc.approvals.list(); -await oc.approvals.respond("approval-id", { decision: "approve" }); -``` - -Task helpers use the durable task ledger that also backs `openclaw tasks`: - -```typescript -const tasks = await oc.tasks.list({ status: "running", sessionKey: "agent:main:main" }); -const task = await oc.tasks.get(tasks.tasks[0].id); -await oc.tasks.cancel(task.task.id, { reason: "user stopped task" }); -``` - -Environment helpers expose read-only Gateway-local and node discovery: - -```typescript -const { environments } = await oc.environments.list(); -await oc.environments.status(environments[0].id); -``` - -## Explicitly unsupported today - -The SDK includes names for the product model we want, but it does not silently -pretend Gateway RPCs exist. These calls currently throw explicit unsupported -errors: - -```typescript -await oc.environments.create({}); -await oc.environments.delete("environment-id"); -``` - -Per-run `workspace`, `runtime`, `environment`, and `approvals` fields are typed -as future shape, but the current Gateway does not support those overrides on -the `agent` RPC. If callers pass them, the SDK throws before submitting the run -so work does not accidentally execute with default workspace, runtime, -environment, or approval behavior. - -## App SDK vs Plugin SDK - -Use the App SDK when code lives outside OpenClaw: - -- Node scripts that start or observe agent runs -- CI jobs that call a Gateway -- dashboards and admin panels -- IDE extensions -- external bridges that do not need to become channel plugins -- integration tests with fake or real Gateway transports - -Use the Plugin SDK when code runs inside OpenClaw: - -- provider plugins -- channel plugins -- tool or lifecycle hooks -- agent harness plugins -- trusted runtime helpers - -App SDK code should import from `@openclaw/sdk`. Plugin code should import from -documented `openclaw/plugin-sdk/*` subpaths. Do not mix the two contracts. - -## Related - -- [OpenClaw App SDK API design](/reference/openclaw-sdk-api-design) -- [Gateway RPC reference](/reference/rpc) -- [Agent loop](/concepts/agent-loop) -- [Agent runtimes](/concepts/agent-runtimes) -- [Sessions](/concepts/session) -- [Background tasks](/automation/tasks) -- [ACP agents](/tools/acp-agents) -- [Plugin SDK overview](/plugins/sdk-overview) diff --git a/docs/docs.json b/docs/docs.json index ce35a56d47cf..e82eeff0e526 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -1741,8 +1741,7 @@ "group": "RPC and API", "pages": [ "reference/rpc", - "concepts/openclaw-sdk", - "reference/openclaw-sdk-api-design", + "gateway/external-apps", "reference/code-mode", "reference/device-models" ] diff --git a/docs/gateway/external-apps.md b/docs/gateway/external-apps.md new file mode 100644 index 000000000000..77685f502652 --- /dev/null +++ b/docs/gateway/external-apps.md @@ -0,0 +1,86 @@ +--- +summary: "Current integration path for external apps, scripts, dashboards, CI jobs, and IDE extensions" +title: "Gateway integrations for external apps" +sidebarTitle: "External apps" +read_when: + - You are building an external app, script, dashboard, CI job, or IDE extension that talks to OpenClaw + - You are choosing between Gateway RPC and the Plugin SDK + - You are integrating with Gateway agent runs, sessions, events, approvals, models, or tools +--- + +External apps should talk to OpenClaw through the Gateway protocol today. Use +Gateway WebSocket and RPC methods when a script, dashboard, CI job, IDE +extension, or another process wants to start agent runs, stream events, wait for +results, cancel work, or inspect Gateway resources. + + + There is no public npm client package yet. Do not add OpenClaw client package + names as application dependencies until release notes announce a published + package and this page includes install instructions. + + + + This page is for code outside the OpenClaw process. Plugin code that runs + inside OpenClaw should use documented `openclaw/plugin-sdk/*` subpaths instead. + + +## What is available today + +| Surface | Status | Use it for | +| --------------------------------------- | ------ | --------------------------------------------------------------------------------------------- | +| [Gateway protocol](/gateway/protocol) | Ready | WebSocket transport, connect handshake, auth scopes, protocol versioning, and events. | +| [Gateway RPC reference](/reference/rpc) | Ready | Current Gateway methods for agents, sessions, tasks, models, tools, artifacts, and approvals. | +| [`openclaw agent`](/cli/agent) | Ready | One-shot script integration when shelling out to the CLI is enough. | +| [`openclaw message`](/cli/message) | Ready | Sending messages or channel actions from scripts. | + +The source tree contains internal package work for a future client library, but +that is not a public install surface. Treat it as preview implementation detail +until the packages are published and versioned. + +## Recommended path + +1. Run or discover a Gateway. +2. Connect over the [Gateway protocol](/gateway/protocol). +3. Call documented RPC methods from [Gateway RPC reference](/reference/rpc). +4. Pin the OpenClaw version you test against. +5. Recheck the RPC reference when upgrading OpenClaw. + +For agent runs, start with the `agent` RPC and pair it with `agent.wait` when +you need a terminal result. For durable conversation state, use the `sessions.*` +methods. For UI integrations, subscribe to Gateway events and render only the +event families your app understands. + +## App code vs plugin code + +Use Gateway RPC when code lives outside OpenClaw: + +- Node scripts that start or observe agent runs +- CI jobs that call a Gateway +- dashboards and admin panels +- IDE extensions +- external bridges that do not need to become channel plugins +- integration tests with fake or real Gateway transports + +Use the Plugin SDK when code runs inside OpenClaw: + +- provider plugins +- channel plugins +- tool or lifecycle hooks +- agent harness plugins +- trusted runtime helpers + +External apps should not import `openclaw/plugin-sdk/*`; those subpaths are for +plugins loaded by OpenClaw. + +## Related + +- [Gateway protocol](/gateway/protocol) +- [Gateway RPC reference](/reference/rpc) +- [CLI agent command](/cli/agent) +- [CLI message command](/cli/message) +- [Agent loop](/concepts/agent-loop) +- [Agent runtimes](/concepts/agent-runtimes) +- [Sessions](/concepts/session) +- [Background tasks](/automation/tasks) +- [ACP agents](/tools/acp-agents) +- [Plugin SDK overview](/plugins/sdk-overview) diff --git a/docs/maturity-scores.yaml b/docs/maturity-scores.yaml index 0beb1afd378e..3092ed3d1add 100644 --- a/docs/maturity-scores.yaml +++ b/docs/maturity-scores.yaml @@ -1803,8 +1803,8 @@ surfaces: supported: false reason: none human_override: false -- id: openclaw-app-sdk - name: OpenClaw App SDK +- id: gateway-external-apps + name: Gateway integrations for external apps family: core level: id: alpha @@ -1831,7 +1831,7 @@ surfaces: source_ref: openclaw@29dd7847fd process_version: 3 categories: - - name: Client API + - name: Gateway API coverage: score: 86 label: Stable diff --git a/docs/plugins/sdk-overview.md b/docs/plugins/sdk-overview.md index 3131c33d584f..5c69e00ce77c 100644 --- a/docs/plugins/sdk-overview.md +++ b/docs/plugins/sdk-overview.md @@ -14,9 +14,8 @@ reference for **what to import** and **what you can register**. This page is for plugin authors using `openclaw/plugin-sdk/*` inside OpenClaw. For external apps, scripts, dashboards, CI jobs, and IDE extensions - that want to run agents through the Gateway, use the - [OpenClaw App SDK](/concepts/openclaw-sdk) and the `@openclaw/sdk` package - instead. + that want to run agents through the Gateway, use + [Gateway integrations for external apps](/gateway/external-apps) instead. diff --git a/docs/reference/openclaw-sdk-api-design.md b/docs/reference/openclaw-sdk-api-design.md deleted file mode 100644 index 96ef4dd40a7b..000000000000 --- a/docs/reference/openclaw-sdk-api-design.md +++ /dev/null @@ -1,390 +0,0 @@ ---- -summary: "Reference design for the public OpenClaw App SDK API, event taxonomy, artifacts, approvals, and package structure" -title: "OpenClaw App SDK API design" -sidebarTitle: "App SDK API design" -read_when: - - You are implementing the proposed public OpenClaw app SDK - - You need the draft namespace, event, result, artifact, approval, or security contract for the app SDK - - You are comparing Gateway protocol resources with the high-level OpenClaw App SDK wrapper ---- - -This page is the detailed API reference design for the public -[OpenClaw App SDK](/concepts/openclaw-sdk). It is intentionally separate from -the [Plugin SDK](/plugins/sdk-overview). - - - `@openclaw/sdk` is the external app/client package for talking to the - Gateway. `openclaw/plugin-sdk/*` is the in-process plugin authoring contract. - Do not import Plugin SDK subpaths from apps that only need to run agents. - - -The public app SDK should be built in two layers: - -1. A low-level generated Gateway client. -2. A high-level ergonomic wrapper with `OpenClaw`, `Agent`, `Session`, `Run`, - `Task`, `Artifact`, `Approval`, and `Environment` objects. - -## Namespace design - -The low-level namespaces should closely follow Gateway resources: - -```typescript -oc.agents.list(); -oc.agents.get("main"); -oc.agents.create(...); -oc.agents.update(...); - -oc.sessions.list(); -oc.sessions.create(...); -oc.sessions.resolve(...); -oc.sessions.send(...); -oc.sessions.messages(...); -oc.sessions.fork(...); -oc.sessions.compact(...); -oc.sessions.abort(...); - -oc.runs.create(...); -oc.runs.get(runId); -oc.runs.events(runId, { after }); -oc.runs.wait(runId); -oc.runs.cancel(runId); - -oc.tasks.list({ status: "running" }); -oc.tasks.get(taskId); -oc.tasks.cancel(taskId, { reason }); -oc.tasks.events(taskId, { after }); // future API - -oc.models.list(); -oc.models.status(); // Gateway models.authStatus - -oc.tools.list(); -oc.tools.invoke("tool-name", { sessionKey, idempotencyKey }); - -oc.artifacts.list({ runId }); -oc.artifacts.get(artifactId, { runId }); -oc.artifacts.download(artifactId, { runId }); - -oc.approvals.list(); -oc.approvals.respond(approvalId, ...); - -oc.environments.list(); -oc.environments.create(...); // future API: current SDK throws unsupported -oc.environments.status(environmentId); -oc.environments.delete(environmentId); // future API: current SDK throws unsupported -``` - -High-level wrappers should return objects that make common flows pleasant: - -```typescript -const run = await agent.run(inputOrParams); -await run.cancel(); -await run.wait(); - -for await (const event of run.events()) { - // normalized event stream -} - -const artifacts = await run.artifacts.list(); -const session = await run.session(); -``` - -## Event contract - -The public SDK should expose versioned, replayable, normalized events. - -```typescript -type OpenClawEvent = { - version: 1; - id: string; - ts: number; - type: OpenClawEventType; - runId?: string; - sessionId?: string; - sessionKey?: string; - taskId?: string; - agentId?: string; - data: unknown; - raw?: unknown; -}; -``` - -`id` is a replay cursor. Consumers should be able to reconnect with -`events({ after: id })` and receive missed events when retention allows. - -Recommended normalized event families: - -| Event | Meaning | -| --------------------- | ----------------------------------------------------------- | -| `run.created` | Run accepted. | -| `run.queued` | Run is waiting for a session lane, runtime, or environment. | -| `run.started` | Runtime started execution. | -| `run.completed` | Run finished successfully. | -| `run.failed` | Run ended with an error. | -| `run.cancelled` | Run was cancelled. | -| `run.timed_out` | Run exceeded its timeout. | -| `assistant.delta` | Assistant text delta. | -| `assistant.message` | Complete assistant message or replacement. | -| `thinking.delta` | Reasoning or plan delta, when policy allows exposure. | -| `tool.call.started` | Tool call began. | -| `tool.call.delta` | Tool call streamed progress or partial output. | -| `tool.call.completed` | Tool call returned successfully. | -| `tool.call.failed` | Tool call failed. | -| `approval.requested` | A run or tool needs approval. | -| `approval.resolved` | Approval was granted, denied, expired, or cancelled. | -| `question.requested` | Runtime asks the user or host app for input. | -| `question.answered` | Host app supplied an answer. | -| `artifact.created` | New artifact available. | -| `artifact.updated` | Existing artifact changed. | -| `session.created` | Session created. | -| `session.updated` | Session metadata changed. | -| `session.compacted` | Session compaction happened. | -| `task.updated` | Background task state changed. | -| `git.branch` | Runtime observed or changed branch state. | -| `git.diff` | Runtime produced or changed a diff. | -| `git.pr` | Runtime opened, updated, or linked a pull request. | - -Runtime-native payloads should be available through `raw`, but apps should not -have to parse `raw` for normal UI. - -## Result contract - -`Run.wait()` should return a stable result envelope: - -```typescript -type RunResult = { - runId: string; - status: "accepted" | "completed" | "failed" | "cancelled" | "timed_out"; - sessionId?: string; - sessionKey?: string; - taskId?: string; - startedAt?: string | number; - endedAt?: string | number; - output?: { - text?: string; - messages?: SDKMessage[]; - }; - usage?: { - inputTokens?: number; - outputTokens?: number; - totalTokens?: number; - costUsd?: number; - }; - artifacts?: ArtifactSummary[]; - error?: SDKError; -}; -``` - -The result should be boring and stable. Timestamp values preserve the Gateway -shape, so current lifecycle-backed runs usually report epoch millisecond -numbers while adapters may still surface ISO strings. Rich UI, tool traces, and -runtime-native details belong in events and artifacts. - -`accepted` is a non-terminal wait result: it means the Gateway wait deadline -expired before the run produced a lifecycle end/error. It must not be treated as -`timed_out`; `timed_out` is reserved for a run that exceeded its own runtime -timeout. - -## Approvals and questions - -Approvals must be first-class because coding agents constantly cross safety -boundaries. - -```typescript -run.onApproval(async (request) => { - if (request.kind === "tool" && request.toolName === "exec") { - return request.approveOnce({ reason: "CI command allowed by policy" }); - } - - return request.askUser(); -}); -``` - -Approval events should carry: - -- approval id -- run id and session id -- request kind -- requested action summary -- tool name or environment action -- risk level -- available decisions -- expiration -- whether the decision can be reused - -Questions are separate from approvals. A question asks the user or host app for -information. An approval asks for permission to perform an action. - -## ToolSpace model - -Apps need to understand the tool surface without importing plugin internals. - -```typescript -const tools = await run.toolSpace(); - -for (const tool of tools.list()) { - console.log(tool.name, tool.source, tool.requiresApproval); -} -``` - -The SDK should expose: - -- normalized tool metadata -- source: OpenClaw, MCP, plugin, channel, runtime, or app -- schema summary -- approval policy -- runtime compatibility -- whether a tool is hidden, readonly, write capable, or host capable - -Tool invocation through the SDK should be explicit and scoped. Most apps should -run agents, not call arbitrary tools directly. - -## Artifact model - -Artifacts should cover more than files. - -```typescript -type ArtifactSummary = { - id: string; - runId?: string; - sessionId?: string; - type: - | "file" - | "patch" - | "diff" - | "log" - | "media" - | "screenshot" - | "trajectory" - | "pull_request" - | "workspace"; - title?: string; - mimeType?: string; - sizeBytes?: number; - createdAt: string; - expiresAt?: string; -}; -``` - -Common examples: - -- file edits and generated files -- patch bundles -- VCS diffs -- screenshots and media outputs -- logs and trace bundles -- pull request links -- runtime trajectories -- managed environment workspace snapshots - -Artifact access should support redaction, retention, and download URLs without -assuming every artifact is a normal local file. - -## Security model - -The app SDK must be explicit about authority. - -Recommended token scopes: - -| Scope | Allows | -| ------------------- | --------------------------------------------------- | -| `agent.read` | List and inspect agents. | -| `agent.run` | Start runs. | -| `session.read` | Read session metadata and messages. | -| `session.write` | Create, send to, fork, compact, and abort sessions. | -| `task.read` | Read background task state. | -| `task.write` | Cancel or modify task notification policy. | -| `approval.respond` | Approve or deny requests. | -| `tools.invoke` | Invoke exposed tools directly. | -| `artifacts.read` | List and download artifacts. | -| `environment.write` | Create or destroy managed environments. | -| `admin` | Administrative operations. | - -Defaults: - -- no secret forwarding by default -- no unrestricted environment variable pass-through -- secret references instead of secret values -- explicit sandbox and network policy -- explicit remote environment retention -- approvals for host execution unless policy proves otherwise -- raw runtime events redacted before they leave Gateway unless the caller has a - stronger diagnostic scope - -## Managed environment provider - -Managed agents should be implemented as environment providers. - -```typescript -type EnvironmentProvider = { - id: string; - capabilities: { - checkout?: boolean; - sandbox?: boolean; - networkPolicy?: boolean; - secrets?: boolean; - artifacts?: boolean; - logs?: boolean; - pullRequests?: boolean; - longRunning?: boolean; - }; -}; -``` - -The first implementation does not need to be a hosted SaaS. It can target -existing node hosts, ephemeral workspaces, CI-style runners, or Testbox-style -environments. The important contract is: - -1. prepare workspace -2. bind safe environment and secrets -3. start run -4. stream events -5. collect artifacts -6. clean up or retain by policy - -Once this is stable, a hosted cloud service can implement the same provider -contract. - -## Package structure - -Recommended packages: - -| Package | Purpose | -| ----------------------- | ------------------------------------------------------------- | -| `@openclaw/sdk` | Public high-level SDK and generated low-level Gateway client. | -| `@openclaw/sdk-react` | Optional React hooks for dashboards and app builders. | -| `@openclaw/sdk-testing` | Test helpers and fake Gateway server for app integrations. | - -The repo already has `openclaw/plugin-sdk/*` for plugins. Keep that namespace -separate to avoid confusing plugin authors with app developers. - -## Generated client strategy - -The low-level client should be generated from versioned Gateway protocol -schemas, then wrapped by handwritten ergonomic classes. - -Layering: - -1. Gateway schema source of truth. -2. Generated low-level TypeScript client. -3. Runtime validators for external inputs and event payloads. -4. High-level `OpenClaw`, `Agent`, `Session`, `Run`, `Task`, and `Artifact` - wrappers. -5. Cookbook examples and integration tests. - -Benefits: - -- protocol drift is visible -- tests can compare generated methods with Gateway exports -- App SDK stays independent from Plugin SDK internals -- low-level consumers still have full protocol access -- high-level consumers get the small product API - -## Related - -- [OpenClaw App SDK](/concepts/openclaw-sdk) -- [Gateway RPC reference](/reference/rpc) -- [Agent loop](/concepts/agent-loop) -- [Agent runtimes](/concepts/agent-runtimes) -- [Background tasks](/automation/tasks) -- [ACP agents](/tools/acp-agents) -- [Plugin SDK overview](/plugins/sdk-overview)