| name | agent-builder |
| description | Build AI-powered DenchClaw apps that interact with the OpenClaw agent โ create chat sessions, send and receive messages with streaming, expose app tools for agent invocation, and access agent memory. |
| metadata | {"openclaw":{"inject":true,"always":true,"emoji":"๐ค"}} |
App Agent Builder
This skill covers building apps that interact with the AI agent. For core app structure and manifest basics, see the parent app-builder skill (app-builder/SKILL.md).
Chat API (agent permission required)
Creating Sessions
const { sessionId } = await dench.chat.createSession("My Analysis Session");
const sessions = await dench.chat.getSessions({ limit: 20 });
Sending Messages with Streaming
const result = await dench.chat.send(sessionId, "Analyze the people table and summarize trends", {
onEvent(event) {
switch (event.type) {
case "text-delta":
appendToChat(event.data);
break;
case "reasoning-delta":
updateThinking(event.data);
break;
case "tool-input-start":
showToolCall(event.name, event.args);
break;
case "tool-output-available":
showToolResult(event.result);
break;
}
},
});
Chat History & Control
const messages = await dench.chat.getHistory(sessionId);
const isActive = await dench.chat.isActive(sessionId);
await dench.chat.abort(sessionId);
Simple Agent Message (fire-and-forget)
await dench.agent.send("Remind me to check the reports tomorrow at 9am");
App-as-Tool (agent permission required)
Apps can expose themselves as tools that the agent can invoke. Declare tools in the manifest:
name: "Chart Analyzer"
permissions:
- agent
tools:
- name: "analyze-chart"
description: "Generates a visual chart analysis from structured data"
inputSchema:
type: object
properties:
data:
type: array
description: "Array of data points"
chartType:
type: string
enum: ["bar", "line", "pie", "scatter"]
required: ["data", "chartType"]
Register tool handlers in the app:
dench.tool.register("analyze-chart", async (input) => {
const { data, chartType } = input;
const chart = renderChart(data, chartType);
const analysis = analyzeData(data);
return {
analysis: analysis,
chartImageUrl: chart.toDataURL(),
summary: `Generated ${chartType} chart with ${data.length} data points`,
};
});
When the agent invokes the tool, the app receives the input, processes it, and returns the result. The app must be open (active tab) for tool invocation to work.
Agent Memory Access (agent permission required)
const memory = await dench.memory.get();
Gateway WebSocket Protocol (Advanced)
For advanced apps that want to build their own chat UI or need direct Gateway access, here's the WebSocket protocol:
Connection
const ws = new WebSocket("ws://127.0.0.1:18789");
Frame Types
{ type: "req", id: "uuid", method: "agent", params: { ... } }
{ type: "res", id: "uuid", ok: true, payload: { ... } }
{ type: "event", event: "agent", seq: 1, payload: { ... } }
Connection Handshake
ws.onopen = () => {
ws.send(
JSON.stringify({
type: "req",
id: crypto.randomUUID(),
method: "connect",
params: {
minProtocol: 3,
maxProtocol: 3,
client: { id: "my-app", version: "1.0", platform: "web", mode: "backend" },
role: "user",
scopes: ["agent", "chat"],
caps: ["tool-events"],
},
}),
);
};
Sending Messages
ws.send(
JSON.stringify({
type: "req",
id: crypto.randomUUID(),
method: "agent",
params: {
message: "Hello, analyze this data",
sessionKey: "agent:main:web:my-session-id",
channel: "webchat",
lane: "web",
},
}),
);
ws.send(
JSON.stringify({
type: "req",
id: crypto.randomUUID(),
method: "chat.abort",
params: { sessionKey: "agent:main:web:my-session-id" },
}),
);
Agent Events
ws.onmessage = (e) => {
const frame = JSON.parse(e.data);
if (frame.type !== "event" || frame.event !== "agent") return;
const { stream, data } = frame.payload;
switch (stream) {
case "lifecycle":
break;
case "thinking":
break;
case "assistant":
break;
case "tool":
break;
}
};
NOTE: For most apps, the bridge chat API (dench.chat.*) is much simpler than direct WebSocket usage. Use the Gateway WS only when you need full control over the connection.
Patterns
Chat UI App
let currentSessionId = null;
const chatContainer = document.getElementById("chat");
async function startNewChat() {
const { sessionId } = await dench.chat.createSession("App Chat");
currentSessionId = sessionId;
chatContainer.innerHTML = "";
}
async function sendMessage(text) {
appendMessage("user", text);
const responseEl = appendMessage("assistant", "");
await dench.chat.send(currentSessionId, text, {
onEvent(event) {
if (event.type === "text-delta") {
responseEl.textContent += event.data;
}
},
});
}
function appendMessage(role, content) {
const div = document.createElement("div");
div.className = `message ${role}`;
div.textContent = content;
chatContainer.appendChild(div);
chatContainer.scrollTop = chatContainer.scrollHeight;
return div;
}
Agent-Powered Data Analysis
async function analyzeData(objectName) {
const schema = await dench.objects.getSchema(objectName);
const { sessionId } = await dench.chat.createSession("Data Analysis");
const result = await dench.chat.send(
sessionId,
`Analyze the ${objectName} object. It has these fields: ${schema.fields.map((f) => f.name).join(", ")}. ` +
`Query the data and provide insights.`,
{
onEvent(event) {
if (event.type === "text-delta") updateAnalysisPanel(event.data);
},
},
);
showFinalAnalysis(result.text);
}