clawport-ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +35 -0
- package/BRANDING.md +131 -0
- package/CLAUDE.md +252 -0
- package/README.md +262 -0
- package/SETUP.md +337 -0
- package/app/agents/[id]/page.tsx +727 -0
- package/app/api/agents/route.ts +12 -0
- package/app/api/chat/[id]/route.ts +139 -0
- package/app/api/cron-runs/route.ts +13 -0
- package/app/api/crons/route.ts +12 -0
- package/app/api/kanban/chat/[id]/route.ts +119 -0
- package/app/api/kanban/chat-history/[ticketId]/route.ts +36 -0
- package/app/api/memory/route.ts +12 -0
- package/app/api/transcribe/route.ts +37 -0
- package/app/api/tts/route.ts +42 -0
- package/app/chat/[id]/page.tsx +10 -0
- package/app/chat/page.tsx +200 -0
- package/app/crons/page.tsx +870 -0
- package/app/docs/page.tsx +399 -0
- package/app/favicon.ico +0 -0
- package/app/globals.css +692 -0
- package/app/kanban/page.tsx +327 -0
- package/app/layout.tsx +45 -0
- package/app/memory/page.tsx +685 -0
- package/app/page.tsx +817 -0
- package/app/providers.tsx +37 -0
- package/app/settings/page.tsx +901 -0
- package/app/settings-provider.tsx +209 -0
- package/components/AgentAvatar.tsx +54 -0
- package/components/AgentNode.tsx +122 -0
- package/components/Breadcrumbs.tsx +126 -0
- package/components/DynamicFavicon.tsx +62 -0
- package/components/ErrorState.tsx +97 -0
- package/components/FeedView.tsx +494 -0
- package/components/GlobalSearch.tsx +571 -0
- package/components/GridView.tsx +532 -0
- package/components/ManorMap.tsx +157 -0
- package/components/MobileSidebar.tsx +251 -0
- package/components/NavLinks.tsx +271 -0
- package/components/OnboardingWizard.tsx +1067 -0
- package/components/Sidebar.tsx +115 -0
- package/components/ThemeToggle.tsx +108 -0
- package/components/chat/AgentList.tsx +537 -0
- package/components/chat/ConversationView.tsx +1047 -0
- package/components/chat/FileAttachment.tsx +140 -0
- package/components/chat/MediaPreview.tsx +111 -0
- package/components/chat/VoiceMessage.tsx +139 -0
- package/components/crons/PipelineGraph.tsx +327 -0
- package/components/crons/WeeklySchedule.tsx +630 -0
- package/components/docs/AgentsSection.tsx +209 -0
- package/components/docs/ApiReferenceSection.tsx +256 -0
- package/components/docs/ArchitectureSection.tsx +221 -0
- package/components/docs/ComponentsSection.tsx +253 -0
- package/components/docs/CronSystemSection.tsx +235 -0
- package/components/docs/DocSection.tsx +346 -0
- package/components/docs/GettingStartedSection.tsx +169 -0
- package/components/docs/ThemingSection.tsx +257 -0
- package/components/docs/TroubleshootingSection.tsx +200 -0
- package/components/kanban/AgentPicker.tsx +321 -0
- package/components/kanban/CreateTicketModal.tsx +333 -0
- package/components/kanban/KanbanBoard.tsx +70 -0
- package/components/kanban/KanbanColumn.tsx +166 -0
- package/components/kanban/TicketCard.tsx +245 -0
- package/components/kanban/TicketDetailPanel.tsx +850 -0
- package/components/ui/badge.tsx +48 -0
- package/components/ui/button.tsx +64 -0
- package/components/ui/card.tsx +92 -0
- package/components/ui/dialog.tsx +158 -0
- package/components/ui/scroll-area.tsx +58 -0
- package/components/ui/separator.tsx +28 -0
- package/components/ui/skeleton.tsx +27 -0
- package/components/ui/tabs.tsx +91 -0
- package/components/ui/tooltip.tsx +57 -0
- package/components.json +23 -0
- package/docs/API.md +648 -0
- package/docs/COMPONENTS.md +1059 -0
- package/docs/THEMING.md +795 -0
- package/lib/agents-registry.ts +35 -0
- package/lib/agents.json +282 -0
- package/lib/agents.test.ts +367 -0
- package/lib/agents.ts +32 -0
- package/lib/anthropic.test.ts +422 -0
- package/lib/anthropic.ts +220 -0
- package/lib/api-error.ts +16 -0
- package/lib/audio-recorder.test.ts +72 -0
- package/lib/audio-recorder.ts +169 -0
- package/lib/conversations.test.ts +331 -0
- package/lib/conversations.ts +117 -0
- package/lib/cron-pipelines.test.ts +69 -0
- package/lib/cron-pipelines.ts +58 -0
- package/lib/cron-runs.test.ts +118 -0
- package/lib/cron-runs.ts +67 -0
- package/lib/cron-utils.test.ts +222 -0
- package/lib/cron-utils.ts +160 -0
- package/lib/crons.test.ts +502 -0
- package/lib/crons.ts +114 -0
- package/lib/env.test.ts +44 -0
- package/lib/env.ts +14 -0
- package/lib/kanban/automation.test.ts +245 -0
- package/lib/kanban/automation.ts +143 -0
- package/lib/kanban/chat-store.test.ts +149 -0
- package/lib/kanban/chat-store.ts +81 -0
- package/lib/kanban/store.test.ts +238 -0
- package/lib/kanban/store.ts +98 -0
- package/lib/kanban/types.ts +50 -0
- package/lib/kanban/useAgentWork.ts +78 -0
- package/lib/memory.ts +45 -0
- package/lib/multimodal.test.ts +219 -0
- package/lib/multimodal.ts +68 -0
- package/lib/pipeline.integration.test.ts +343 -0
- package/lib/sanitize.ts +194 -0
- package/lib/settings.test.ts +137 -0
- package/lib/settings.ts +94 -0
- package/lib/styles.ts +24 -0
- package/lib/themes.ts +9 -0
- package/lib/transcribe.test.ts +141 -0
- package/lib/transcribe.ts +111 -0
- package/lib/types.ts +66 -0
- package/lib/utils.ts +6 -0
- package/lib/validation.test.ts +132 -0
- package/lib/validation.ts +80 -0
- package/next.config.ts +7 -0
- package/package.json +56 -0
- package/postcss.config.mjs +7 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/next.svg +1 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/scripts/setup.mjs +215 -0
- package/tsconfig.json +34 -0
- package/vitest.config.ts +17 -0
package/docs/API.md
ADDED
|
@@ -0,0 +1,648 @@
|
|
|
1
|
+
# ClawPort -- API Reference
|
|
2
|
+
|
|
3
|
+
All API routes are Next.js App Router route handlers under `app/api/`.
|
|
4
|
+
The base URL during development is `http://localhost:3000`.
|
|
5
|
+
|
|
6
|
+
## Prerequisites
|
|
7
|
+
|
|
8
|
+
| Dependency | Required By | Notes |
|
|
9
|
+
|---|---|---|
|
|
10
|
+
| OpenClaw gateway (`localhost:18789`) | `/api/chat/[id]`, `/api/tts`, `/api/transcribe`, `/api/kanban/chat/[id]` | Must be running for any AI-powered route |
|
|
11
|
+
| `WORKSPACE_PATH` env var | `/api/agents`, `/api/memory`, `/api/cron-runs`, `/api/kanban/chat-history/[ticketId]` | Filesystem path to `.openclaw/workspace` |
|
|
12
|
+
| `OPENCLAW_BIN` env var | `/api/crons`, `/api/chat/[id]` (vision path) | Path to the `openclaw` CLI binary |
|
|
13
|
+
| `OPENCLAW_GATEWAY_TOKEN` env var | All gateway-dependent routes | Auth token for the OpenClaw gateway |
|
|
14
|
+
|
|
15
|
+
## Error Format
|
|
16
|
+
|
|
17
|
+
All error responses share a consistent JSON shape:
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{ "error": "Human-readable error message" }
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Returned with the appropriate HTTP status code and `Content-Type: application/json`.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Routes
|
|
28
|
+
|
|
29
|
+
### GET `/api/agents`
|
|
30
|
+
|
|
31
|
+
Returns the full list of registered agents, each with their SOUL.md content loaded from the filesystem.
|
|
32
|
+
|
|
33
|
+
**Data source:** JSON registry file (bundled `lib/agents.json` or user override at `$WORKSPACE_PATH/clawport/agents.json`) + SOUL.md files from the workspace filesystem.
|
|
34
|
+
|
|
35
|
+
#### Request
|
|
36
|
+
|
|
37
|
+
No parameters.
|
|
38
|
+
|
|
39
|
+
#### Response
|
|
40
|
+
|
|
41
|
+
| Status | Content-Type | Body |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| 200 | `application/json` | `Agent[]` |
|
|
44
|
+
| 500 | `application/json` | `{ "error": string }` |
|
|
45
|
+
|
|
46
|
+
**`Agent` schema:**
|
|
47
|
+
|
|
48
|
+
| Field | Type | Description |
|
|
49
|
+
|---|---|---|
|
|
50
|
+
| `id` | `string` | Slug identifier (e.g. `"vera"`) |
|
|
51
|
+
| `name` | `string` | Display name (e.g. `"VERA"`) |
|
|
52
|
+
| `title` | `string` | Role title (e.g. `"Chief Strategy Officer"`) |
|
|
53
|
+
| `reportsTo` | `string \| null` | Parent agent ID, or `null` for the root |
|
|
54
|
+
| `directReports` | `string[]` | Child agent IDs |
|
|
55
|
+
| `soulPath` | `string \| null` | Path to the agent's SOUL.md file |
|
|
56
|
+
| `soul` | `string \| null` | Full SOUL.md content (loaded at request time), or `null` if file not found |
|
|
57
|
+
| `voiceId` | `string \| null` | ElevenLabs voice ID |
|
|
58
|
+
| `color` | `string` | Hex color for the org chart node |
|
|
59
|
+
| `emoji` | `string` | Emoji identifier |
|
|
60
|
+
| `tools` | `string[]` | Tools this agent has access to |
|
|
61
|
+
| `crons` | `CronJob[]` | Always `[]` from this endpoint (populated client-side) |
|
|
62
|
+
| `memoryPath` | `string \| null` | Path to the agent's memory file |
|
|
63
|
+
| `description` | `string` | One-liner description of the agent |
|
|
64
|
+
|
|
65
|
+
#### Example
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
curl http://localhost:3000/api/agents
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```js
|
|
72
|
+
const res = await fetch('/api/agents')
|
|
73
|
+
const agents = await res.json()
|
|
74
|
+
// agents[0].id => "jarvis"
|
|
75
|
+
// agents[0].soul => "# JARVIS\n\nYou are the team's orchestrator..."
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
### POST `/api/chat/[id]`
|
|
81
|
+
|
|
82
|
+
Send a chat message to an agent and receive a streaming response. This route has **two pipelines** depending on whether the latest user message contains images.
|
|
83
|
+
|
|
84
|
+
**Requires:** OpenClaw gateway running at `localhost:18789`.
|
|
85
|
+
|
|
86
|
+
#### Path Parameters
|
|
87
|
+
|
|
88
|
+
| Param | Type | Description |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| `id` | `string` | Agent ID (must match a registered agent) |
|
|
91
|
+
|
|
92
|
+
#### Request Body
|
|
93
|
+
|
|
94
|
+
| Field | Type | Required | Description |
|
|
95
|
+
|---|---|---|---|
|
|
96
|
+
| `messages` | `ApiMessage[]` | Yes | Conversation history |
|
|
97
|
+
| `operatorName` | `string` | No | Name shown to the agent as its operator. Defaults to `"Operator"` |
|
|
98
|
+
|
|
99
|
+
**`ApiMessage` schema:**
|
|
100
|
+
|
|
101
|
+
| Field | Type | Description |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| `role` | `"user" \| "assistant" \| "system"` | Message role |
|
|
104
|
+
| `content` | `string \| ContentPart[]` | Plain text or multimodal content array |
|
|
105
|
+
|
|
106
|
+
**`ContentPart` variants:**
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
{ type: "text", text: string }
|
|
110
|
+
{ type: "image_url", image_url: { url: string } }
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Image URLs must be base64 data URLs (e.g. `data:image/jpeg;base64,...`). Client-side images should be resized to 1200px max before encoding to avoid exceeding macOS ARG_MAX.
|
|
114
|
+
|
|
115
|
+
#### Pipeline 1: Text Streaming
|
|
116
|
+
|
|
117
|
+
Used when the latest user message does **not** contain images.
|
|
118
|
+
|
|
119
|
+
The route creates a streaming chat completion via the OpenAI SDK pointed at the gateway (`localhost:18789/v1/chat/completions`) using model `claude-sonnet-4-6`.
|
|
120
|
+
|
|
121
|
+
**Response:** Server-Sent Events (`text/event-stream`).
|
|
122
|
+
|
|
123
|
+
Each SSE data line is a JSON object with a `content` field containing the next token:
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
data: {"content":"Hello"}
|
|
127
|
+
|
|
128
|
+
data: {"content":" there"}
|
|
129
|
+
|
|
130
|
+
data: [DONE]
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
#### Pipeline 2: Vision (send + poll)
|
|
134
|
+
|
|
135
|
+
Used when the latest user message **does** contain `image_url` content parts and `OPENCLAW_GATEWAY_TOKEN` is set.
|
|
136
|
+
|
|
137
|
+
The gateway's `/v1/chat/completions` endpoint strips image content, so vision messages go through the CLI agent pipeline instead:
|
|
138
|
+
|
|
139
|
+
1. Images are extracted and converted to `{ mimeType, content (base64) }` attachments.
|
|
140
|
+
2. `openclaw gateway call chat.send` is invoked via `execFile` to send the message asynchronously.
|
|
141
|
+
3. The route polls `openclaw gateway call chat.history` every 2 seconds (up to 60s timeout) until the assistant's response appears.
|
|
142
|
+
4. The complete response is returned as a single SSE frame followed by `[DONE]`.
|
|
143
|
+
|
|
144
|
+
**Response:** Same SSE format as Pipeline 1, but the entire response arrives in a single `data:` frame rather than streamed token-by-token.
|
|
145
|
+
|
|
146
|
+
#### Response Summary
|
|
147
|
+
|
|
148
|
+
| Status | Content-Type | Body |
|
|
149
|
+
|---|---|---|
|
|
150
|
+
| 200 | `text/event-stream` | SSE stream (both pipelines) |
|
|
151
|
+
| 400 | `application/json` | `{ "error": string }` -- invalid JSON or failed message validation |
|
|
152
|
+
| 404 | `application/json` | `{ "error": "Agent not found" }` |
|
|
153
|
+
| 500 | `application/json` | `{ "error": "Chat failed. Make sure OpenClaw gateway is running." }` |
|
|
154
|
+
|
|
155
|
+
#### Example
|
|
156
|
+
|
|
157
|
+
```js
|
|
158
|
+
// Text message
|
|
159
|
+
const res = await fetch('/api/chat/jarvis', {
|
|
160
|
+
method: 'POST',
|
|
161
|
+
headers: { 'Content-Type': 'application/json' },
|
|
162
|
+
body: JSON.stringify({
|
|
163
|
+
operatorName: 'John',
|
|
164
|
+
messages: [
|
|
165
|
+
{ role: 'user', content: 'What cron jobs are running today?' }
|
|
166
|
+
]
|
|
167
|
+
})
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
const reader = res.body.getReader()
|
|
171
|
+
const decoder = new TextDecoder()
|
|
172
|
+
while (true) {
|
|
173
|
+
const { done, value } = await reader.read()
|
|
174
|
+
if (done) break
|
|
175
|
+
const text = decoder.decode(value)
|
|
176
|
+
// Parse SSE lines: "data: {\"content\":\"...\"}\n\n"
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
```js
|
|
181
|
+
// Vision message (image)
|
|
182
|
+
const res = await fetch('/api/chat/vera', {
|
|
183
|
+
method: 'POST',
|
|
184
|
+
headers: { 'Content-Type': 'application/json' },
|
|
185
|
+
body: JSON.stringify({
|
|
186
|
+
messages: [
|
|
187
|
+
{
|
|
188
|
+
role: 'user',
|
|
189
|
+
content: [
|
|
190
|
+
{ type: 'text', text: 'What do you see in this screenshot?' },
|
|
191
|
+
{ type: 'image_url', image_url: { url: 'data:image/jpeg;base64,/9j/4AAQ...' } }
|
|
192
|
+
]
|
|
193
|
+
}
|
|
194
|
+
]
|
|
195
|
+
})
|
|
196
|
+
})
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
### GET `/api/crons`
|
|
202
|
+
|
|
203
|
+
Returns all cron jobs registered with OpenClaw, enriched with schedule descriptions, agent ownership, and delivery config.
|
|
204
|
+
|
|
205
|
+
**Data source:** Runs `openclaw cron list --json` via the CLI (`OPENCLAW_BIN` required).
|
|
206
|
+
|
|
207
|
+
#### Request
|
|
208
|
+
|
|
209
|
+
No parameters.
|
|
210
|
+
|
|
211
|
+
#### Response
|
|
212
|
+
|
|
213
|
+
| Status | Content-Type | Body |
|
|
214
|
+
|---|---|---|
|
|
215
|
+
| 200 | `application/json` | `CronJob[]` |
|
|
216
|
+
| 500 | `application/json` | `{ "error": string }` |
|
|
217
|
+
|
|
218
|
+
**`CronJob` schema:**
|
|
219
|
+
|
|
220
|
+
| Field | Type | Description |
|
|
221
|
+
|---|---|---|
|
|
222
|
+
| `id` | `string` | Job identifier |
|
|
223
|
+
| `name` | `string` | Job name (used to match owning agent by prefix) |
|
|
224
|
+
| `schedule` | `string` | Raw cron expression |
|
|
225
|
+
| `scheduleDescription` | `string` | Human-readable (e.g. `"Daily at 8 AM"`) |
|
|
226
|
+
| `timezone` | `string \| null` | Timezone from schedule object, if present |
|
|
227
|
+
| `status` | `"ok" \| "error" \| "idle"` | Last run outcome |
|
|
228
|
+
| `lastRun` | `string \| null` | ISO 8601 timestamp of last execution |
|
|
229
|
+
| `nextRun` | `string \| null` | ISO 8601 timestamp of next scheduled run |
|
|
230
|
+
| `lastError` | `string \| null` | Error message from last failed run |
|
|
231
|
+
| `agentId` | `string \| null` | Owning agent ID (matched by job name prefix) |
|
|
232
|
+
| `description` | `string \| null` | Job description |
|
|
233
|
+
| `enabled` | `boolean` | Whether the job is active |
|
|
234
|
+
| `delivery` | `CronDelivery \| null` | Delivery config (mode, channel, to) |
|
|
235
|
+
| `lastDurationMs` | `number \| null` | Duration of last run in milliseconds |
|
|
236
|
+
| `consecutiveErrors` | `number` | Count of consecutive failed runs |
|
|
237
|
+
| `lastDeliveryStatus` | `string \| null` | Delivery outcome of last run |
|
|
238
|
+
|
|
239
|
+
**`CronDelivery` schema:**
|
|
240
|
+
|
|
241
|
+
| Field | Type | Description |
|
|
242
|
+
|---|---|---|
|
|
243
|
+
| `mode` | `string` | Delivery mode |
|
|
244
|
+
| `channel` | `string` | Delivery channel |
|
|
245
|
+
| `to` | `string \| null` | Delivery recipient |
|
|
246
|
+
|
|
247
|
+
#### Example
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
curl http://localhost:3000/api/crons
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
### GET `/api/cron-runs`
|
|
256
|
+
|
|
257
|
+
Returns cron run history parsed from JSONL log files on the filesystem. Results are sorted newest-first.
|
|
258
|
+
|
|
259
|
+
**Data source:** Reads `.jsonl` files from `$WORKSPACE_PATH/../cron/runs/`.
|
|
260
|
+
|
|
261
|
+
#### Query Parameters
|
|
262
|
+
|
|
263
|
+
| Param | Type | Required | Description |
|
|
264
|
+
|---|---|---|---|
|
|
265
|
+
| `jobId` | `string` | No | Filter to runs for a specific job. When provided, reads only `{jobId}.jsonl`. When omitted, reads all `.jsonl` files in the runs directory. |
|
|
266
|
+
|
|
267
|
+
#### Response
|
|
268
|
+
|
|
269
|
+
| Status | Content-Type | Body |
|
|
270
|
+
|---|---|---|
|
|
271
|
+
| 200 | `application/json` | `CronRun[]` |
|
|
272
|
+
| 500 | `application/json` | `{ "error": string }` |
|
|
273
|
+
|
|
274
|
+
**`CronRun` schema:**
|
|
275
|
+
|
|
276
|
+
| Field | Type | Description |
|
|
277
|
+
|---|---|---|
|
|
278
|
+
| `ts` | `number` | Unix timestamp (milliseconds) of the run |
|
|
279
|
+
| `jobId` | `string` | Job identifier |
|
|
280
|
+
| `status` | `"ok" \| "error"` | Run outcome |
|
|
281
|
+
| `summary` | `string \| null` | Summary of what the run produced |
|
|
282
|
+
| `error` | `string \| null` | Error message if the run failed |
|
|
283
|
+
| `durationMs` | `number` | Duration in milliseconds |
|
|
284
|
+
| `deliveryStatus` | `string \| null` | Delivery outcome |
|
|
285
|
+
|
|
286
|
+
#### Example
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
# All runs
|
|
290
|
+
curl http://localhost:3000/api/cron-runs
|
|
291
|
+
|
|
292
|
+
# Runs for a specific job
|
|
293
|
+
curl "http://localhost:3000/api/cron-runs?jobId=pulse-daily-digest"
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
### GET `/api/memory`
|
|
299
|
+
|
|
300
|
+
Returns the contents of key memory files from the workspace: long-term memory, team memory, team intel, and the daily logs for today and yesterday.
|
|
301
|
+
|
|
302
|
+
**Data source:** Reads specific files from the `$WORKSPACE_PATH` filesystem directory.
|
|
303
|
+
|
|
304
|
+
Files checked (in order):
|
|
305
|
+
1. `$WORKSPACE_PATH/MEMORY.md` -- Long-Term Memory (Jarvis)
|
|
306
|
+
2. `$WORKSPACE_PATH/memory/team-memory.md` -- Team Memory
|
|
307
|
+
3. `$WORKSPACE_PATH/memory/team-intel.json` -- Team Intel (JSON)
|
|
308
|
+
4. `$WORKSPACE_PATH/memory/{YYYY-MM-DD}.md` -- Daily Log (Today)
|
|
309
|
+
5. `$WORKSPACE_PATH/memory/{YYYY-MM-DD}.md` -- Daily Log (Yesterday)
|
|
310
|
+
|
|
311
|
+
Only files that exist are included in the response.
|
|
312
|
+
|
|
313
|
+
#### Request
|
|
314
|
+
|
|
315
|
+
No parameters.
|
|
316
|
+
|
|
317
|
+
#### Response
|
|
318
|
+
|
|
319
|
+
| Status | Content-Type | Body |
|
|
320
|
+
|---|---|---|
|
|
321
|
+
| 200 | `application/json` | `MemoryFile[]` |
|
|
322
|
+
| 500 | `application/json` | `{ "error": string }` |
|
|
323
|
+
|
|
324
|
+
**`MemoryFile` schema:**
|
|
325
|
+
|
|
326
|
+
| Field | Type | Description |
|
|
327
|
+
|---|---|---|
|
|
328
|
+
| `label` | `string` | Human-readable label (e.g. `"Long-Term Memory (Jarvis)"`) |
|
|
329
|
+
| `path` | `string` | Absolute filesystem path to the file |
|
|
330
|
+
| `content` | `string` | Full file contents |
|
|
331
|
+
| `lastModified` | `string` | ISO 8601 timestamp of last modification |
|
|
332
|
+
|
|
333
|
+
#### Example
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
curl http://localhost:3000/api/memory
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
```js
|
|
340
|
+
const res = await fetch('/api/memory')
|
|
341
|
+
const files = await res.json()
|
|
342
|
+
// files[0].label => "Long-Term Memory (Jarvis)"
|
|
343
|
+
// files[0].content => "# Memory\n\n..."
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
### POST `/api/tts`
|
|
349
|
+
|
|
350
|
+
Converts text to speech audio using the OpenClaw gateway's TTS endpoint (OpenAI-compatible `audio.speech` API).
|
|
351
|
+
|
|
352
|
+
**Requires:** OpenClaw gateway running at `localhost:18789`.
|
|
353
|
+
|
|
354
|
+
#### Request Body
|
|
355
|
+
|
|
356
|
+
| Field | Type | Required | Description |
|
|
357
|
+
|---|---|---|---|
|
|
358
|
+
| `text` | `string` | Yes | The text to synthesize |
|
|
359
|
+
| `voice` | `string` | No | Voice identifier. Defaults to `"alloy"` |
|
|
360
|
+
|
|
361
|
+
#### Response
|
|
362
|
+
|
|
363
|
+
| Status | Content-Type | Body |
|
|
364
|
+
|---|---|---|
|
|
365
|
+
| 200 | `audio/mpeg` | Raw MP3 audio bytes |
|
|
366
|
+
| 400 | `application/json` | `{ "error": "Missing or invalid \"text\" field" }` |
|
|
367
|
+
| 500 | `application/json` | `{ "error": "TTS failed. Make sure OpenClaw gateway is running." }` |
|
|
368
|
+
|
|
369
|
+
The `Content-Length` header is set on successful responses.
|
|
370
|
+
|
|
371
|
+
#### Example
|
|
372
|
+
|
|
373
|
+
```bash
|
|
374
|
+
curl -X POST http://localhost:3000/api/tts \
|
|
375
|
+
-H 'Content-Type: application/json' \
|
|
376
|
+
-d '{"text": "Hello from Jarvis", "voice": "alloy"}' \
|
|
377
|
+
--output speech.mp3
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
```js
|
|
381
|
+
const res = await fetch('/api/tts', {
|
|
382
|
+
method: 'POST',
|
|
383
|
+
headers: { 'Content-Type': 'application/json' },
|
|
384
|
+
body: JSON.stringify({ text: 'Hello from Jarvis', voice: 'nova' })
|
|
385
|
+
})
|
|
386
|
+
const audioBlob = await res.blob()
|
|
387
|
+
const audioUrl = URL.createObjectURL(audioBlob)
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
### POST `/api/transcribe`
|
|
393
|
+
|
|
394
|
+
Transcribes audio to text using the OpenClaw gateway's Whisper endpoint (OpenAI-compatible `audio.transcriptions` API).
|
|
395
|
+
|
|
396
|
+
**Requires:** OpenClaw gateway running at `localhost:18789`.
|
|
397
|
+
|
|
398
|
+
#### Request Body
|
|
399
|
+
|
|
400
|
+
Multipart form data (`multipart/form-data`).
|
|
401
|
+
|
|
402
|
+
| Field | Type | Required | Description |
|
|
403
|
+
|---|---|---|---|
|
|
404
|
+
| `audio` | `File` | Yes | Audio file (webm, mp4, wav, etc.) |
|
|
405
|
+
|
|
406
|
+
#### Response
|
|
407
|
+
|
|
408
|
+
| Status | Content-Type | Body |
|
|
409
|
+
|---|---|---|
|
|
410
|
+
| 200 | `application/json` | `{ "text": string }` |
|
|
411
|
+
| 400 | `application/json` | `{ "error": "Expected multipart form data" }` or `{ "error": "Missing audio file" }` |
|
|
412
|
+
| 500 | `application/json` | `{ "error": "Transcription failed. Check OpenClaw gateway." }` |
|
|
413
|
+
|
|
414
|
+
#### Example
|
|
415
|
+
|
|
416
|
+
```bash
|
|
417
|
+
curl -X POST http://localhost:3000/api/transcribe \
|
|
418
|
+
-F 'audio=@recording.webm'
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
```js
|
|
422
|
+
const formData = new FormData()
|
|
423
|
+
formData.append('audio', audioBlob, 'recording.webm')
|
|
424
|
+
|
|
425
|
+
const res = await fetch('/api/transcribe', { method: 'POST', body: formData })
|
|
426
|
+
const { text } = await res.json()
|
|
427
|
+
// text => "Hello, what are the latest metrics?"
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
---
|
|
431
|
+
|
|
432
|
+
### POST `/api/kanban/chat/[id]`
|
|
433
|
+
|
|
434
|
+
Send a chat message to an agent in the context of a kanban ticket. Similar to the main chat route but includes ticket context in the system prompt. Text-only (no vision pipeline).
|
|
435
|
+
|
|
436
|
+
**Requires:** OpenClaw gateway running at `localhost:18789`.
|
|
437
|
+
|
|
438
|
+
#### Path Parameters
|
|
439
|
+
|
|
440
|
+
| Param | Type | Description |
|
|
441
|
+
|---|---|---|
|
|
442
|
+
| `id` | `string` | Agent ID (must match a registered agent) |
|
|
443
|
+
|
|
444
|
+
#### Request Body
|
|
445
|
+
|
|
446
|
+
| Field | Type | Required | Description |
|
|
447
|
+
|---|---|---|---|
|
|
448
|
+
| `messages` | `KanbanMessage[]` | Yes | Conversation history |
|
|
449
|
+
| `ticket` | `Ticket` | No | Ticket context to include in the system prompt |
|
|
450
|
+
|
|
451
|
+
**`KanbanMessage` schema:**
|
|
452
|
+
|
|
453
|
+
| Field | Type | Description |
|
|
454
|
+
|---|---|---|
|
|
455
|
+
| `role` | `"user" \| "assistant"` | Message role |
|
|
456
|
+
| `content` | `string` | Message text |
|
|
457
|
+
|
|
458
|
+
**`Ticket` schema:**
|
|
459
|
+
|
|
460
|
+
| Field | Type | Description |
|
|
461
|
+
|---|---|---|
|
|
462
|
+
| `title` | `string` | Ticket title |
|
|
463
|
+
| `description` | `string` | Ticket description |
|
|
464
|
+
| `status` | `string` | Current status |
|
|
465
|
+
| `priority` | `string` | Priority level |
|
|
466
|
+
| `assigneeRole` | `string \| null` | Role of the assigned agent |
|
|
467
|
+
| `workResult` | `string \| null` | Previous work output (included in prompt so the agent can reference it) |
|
|
468
|
+
|
|
469
|
+
#### Response
|
|
470
|
+
|
|
471
|
+
| Status | Content-Type | Body |
|
|
472
|
+
|---|---|---|
|
|
473
|
+
| 200 | `text/event-stream` | SSE stream (same format as `/api/chat/[id]`) |
|
|
474
|
+
| 400 | `application/json` | `{ "error": string }` -- invalid JSON or messages not an array |
|
|
475
|
+
| 404 | `application/json` | `{ "error": "Agent not found" }` |
|
|
476
|
+
| 500 | `application/json` | `{ "error": "Chat failed. Make sure OpenClaw gateway is running." }` |
|
|
477
|
+
|
|
478
|
+
SSE format is identical to the main chat route's text pipeline:
|
|
479
|
+
|
|
480
|
+
```
|
|
481
|
+
data: {"content":"I see this ticket is about..."}
|
|
482
|
+
|
|
483
|
+
data: {"content":" the daily digest."}
|
|
484
|
+
|
|
485
|
+
data: [DONE]
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
#### Example
|
|
489
|
+
|
|
490
|
+
```js
|
|
491
|
+
const res = await fetch('/api/kanban/chat/pulse', {
|
|
492
|
+
method: 'POST',
|
|
493
|
+
headers: { 'Content-Type': 'application/json' },
|
|
494
|
+
body: JSON.stringify({
|
|
495
|
+
messages: [
|
|
496
|
+
{ role: 'user', content: 'What is the status of this ticket?' }
|
|
497
|
+
],
|
|
498
|
+
ticket: {
|
|
499
|
+
title: 'Fix daily digest formatting',
|
|
500
|
+
description: 'The email digest has broken HTML in the header.',
|
|
501
|
+
status: 'in-progress',
|
|
502
|
+
priority: 'high',
|
|
503
|
+
assigneeRole: 'pulse',
|
|
504
|
+
workResult: null
|
|
505
|
+
}
|
|
506
|
+
})
|
|
507
|
+
})
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
---
|
|
511
|
+
|
|
512
|
+
### GET `/api/kanban/chat-history/[ticketId]`
|
|
513
|
+
|
|
514
|
+
Retrieve the persisted chat history for a kanban ticket.
|
|
515
|
+
|
|
516
|
+
**Data source:** Reads from `$WORKSPACE_PATH/../kanban/chats/{ticketId}.jsonl` on the filesystem.
|
|
517
|
+
|
|
518
|
+
#### Path Parameters
|
|
519
|
+
|
|
520
|
+
| Param | Type | Description |
|
|
521
|
+
|---|---|---|
|
|
522
|
+
| `ticketId` | `string` | Ticket identifier |
|
|
523
|
+
|
|
524
|
+
#### Response
|
|
525
|
+
|
|
526
|
+
| Status | Content-Type | Body |
|
|
527
|
+
|---|---|---|
|
|
528
|
+
| 200 | `application/json` | `StoredChatMessage[]` (sorted oldest-first) |
|
|
529
|
+
| 500 | `application/json` | `{ "error": string }` |
|
|
530
|
+
|
|
531
|
+
Returns an empty array `[]` if no chat history file exists for the ticket.
|
|
532
|
+
|
|
533
|
+
**`StoredChatMessage` schema:**
|
|
534
|
+
|
|
535
|
+
| Field | Type | Description |
|
|
536
|
+
|---|---|---|
|
|
537
|
+
| `id` | `string` | Unique message identifier |
|
|
538
|
+
| `role` | `"user" \| "assistant"` | Message role |
|
|
539
|
+
| `content` | `string` | Message text |
|
|
540
|
+
| `timestamp` | `number` | Unix timestamp (milliseconds) |
|
|
541
|
+
|
|
542
|
+
#### Example
|
|
543
|
+
|
|
544
|
+
```bash
|
|
545
|
+
curl http://localhost:3000/api/kanban/chat-history/ticket-abc-123
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
---
|
|
549
|
+
|
|
550
|
+
### POST `/api/kanban/chat-history/[ticketId]`
|
|
551
|
+
|
|
552
|
+
Append chat messages to the persisted history for a kanban ticket. Creates the chats directory and JSONL file if they do not exist.
|
|
553
|
+
|
|
554
|
+
**Data source:** Appends to `$WORKSPACE_PATH/../kanban/chats/{ticketId}.jsonl` on the filesystem.
|
|
555
|
+
|
|
556
|
+
#### Path Parameters
|
|
557
|
+
|
|
558
|
+
| Param | Type | Description |
|
|
559
|
+
|---|---|---|
|
|
560
|
+
| `ticketId` | `string` | Ticket identifier |
|
|
561
|
+
|
|
562
|
+
#### Request Body
|
|
563
|
+
|
|
564
|
+
| Field | Type | Required | Description |
|
|
565
|
+
|---|---|---|---|
|
|
566
|
+
| `messages` | `StoredChatMessage[]` | Yes | Messages to append (must be a non-empty array) |
|
|
567
|
+
|
|
568
|
+
See `StoredChatMessage` schema in the GET endpoint above.
|
|
569
|
+
|
|
570
|
+
#### Response
|
|
571
|
+
|
|
572
|
+
| Status | Content-Type | Body |
|
|
573
|
+
|---|---|---|
|
|
574
|
+
| 200 | `application/json` | `{ "ok": true }` |
|
|
575
|
+
| 400 | `application/json` | `{ "error": "messages array required" }` |
|
|
576
|
+
| 500 | `application/json` | `{ "error": string }` |
|
|
577
|
+
|
|
578
|
+
#### Example
|
|
579
|
+
|
|
580
|
+
```js
|
|
581
|
+
await fetch('/api/kanban/chat-history/ticket-abc-123', {
|
|
582
|
+
method: 'POST',
|
|
583
|
+
headers: { 'Content-Type': 'application/json' },
|
|
584
|
+
body: JSON.stringify({
|
|
585
|
+
messages: [
|
|
586
|
+
{ id: 'msg-1', role: 'user', content: 'Can you look into this?', timestamp: 1709400000000 },
|
|
587
|
+
{ id: 'msg-2', role: 'assistant', content: 'On it.', timestamp: 1709400005000 }
|
|
588
|
+
]
|
|
589
|
+
})
|
|
590
|
+
})
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
---
|
|
594
|
+
|
|
595
|
+
## Route Summary
|
|
596
|
+
|
|
597
|
+
| Method | Endpoint | Gateway Required | Data Source | Content-Type |
|
|
598
|
+
|---|---|---|---|---|
|
|
599
|
+
| GET | `/api/agents` | No | Filesystem (JSON + SOUL.md) | `application/json` |
|
|
600
|
+
| POST | `/api/chat/[id]` | Yes | Gateway (streaming) or CLI (vision) | `text/event-stream` |
|
|
601
|
+
| GET | `/api/crons` | No | CLI (`openclaw cron list`) | `application/json` |
|
|
602
|
+
| GET | `/api/cron-runs` | No | Filesystem (JSONL) | `application/json` |
|
|
603
|
+
| GET | `/api/memory` | No | Filesystem (Markdown/JSON) | `application/json` |
|
|
604
|
+
| POST | `/api/tts` | Yes | Gateway (`audio.speech`) | `audio/mpeg` |
|
|
605
|
+
| POST | `/api/transcribe` | Yes | Gateway (`audio.transcriptions`) | `application/json` |
|
|
606
|
+
| POST | `/api/kanban/chat/[id]` | Yes | Gateway (streaming) | `text/event-stream` |
|
|
607
|
+
| GET | `/api/kanban/chat-history/[ticketId]` | No | Filesystem (JSONL) | `application/json` |
|
|
608
|
+
| POST | `/api/kanban/chat-history/[ticketId]` | No | Filesystem (JSONL) | `application/json` |
|
|
609
|
+
|
|
610
|
+
## SSE Stream Protocol
|
|
611
|
+
|
|
612
|
+
All streaming chat endpoints (`/api/chat/[id]` and `/api/kanban/chat/[id]`) use the same Server-Sent Events protocol:
|
|
613
|
+
|
|
614
|
+
1. Each data frame is a JSON object: `data: {"content":"token text"}\n\n`
|
|
615
|
+
2. The stream terminates with: `data: [DONE]\n\n`
|
|
616
|
+
3. Content-Type is `text/event-stream` with `Cache-Control: no-cache` and `Connection: keep-alive`.
|
|
617
|
+
4. If a stream error occurs mid-response, the server sends `[DONE]` and closes the connection (no error frame is sent).
|
|
618
|
+
|
|
619
|
+
### Client-side consumption pattern
|
|
620
|
+
|
|
621
|
+
```js
|
|
622
|
+
async function readStream(response) {
|
|
623
|
+
const reader = response.body.getReader()
|
|
624
|
+
const decoder = new TextDecoder()
|
|
625
|
+
let fullText = ''
|
|
626
|
+
|
|
627
|
+
while (true) {
|
|
628
|
+
const { done, value } = await reader.read()
|
|
629
|
+
if (done) break
|
|
630
|
+
|
|
631
|
+
const chunk = decoder.decode(value, { stream: true })
|
|
632
|
+
const lines = chunk.split('\n')
|
|
633
|
+
|
|
634
|
+
for (const line of lines) {
|
|
635
|
+
if (line.startsWith('data: ')) {
|
|
636
|
+
const payload = line.slice(6)
|
|
637
|
+
if (payload === '[DONE]') return fullText
|
|
638
|
+
try {
|
|
639
|
+
const { content } = JSON.parse(payload)
|
|
640
|
+
fullText += content
|
|
641
|
+
} catch { /* skip malformed frames */ }
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
return fullText
|
|
647
|
+
}
|
|
648
|
+
```
|