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.
Files changed (132) hide show
  1. package/.env.example +35 -0
  2. package/BRANDING.md +131 -0
  3. package/CLAUDE.md +252 -0
  4. package/README.md +262 -0
  5. package/SETUP.md +337 -0
  6. package/app/agents/[id]/page.tsx +727 -0
  7. package/app/api/agents/route.ts +12 -0
  8. package/app/api/chat/[id]/route.ts +139 -0
  9. package/app/api/cron-runs/route.ts +13 -0
  10. package/app/api/crons/route.ts +12 -0
  11. package/app/api/kanban/chat/[id]/route.ts +119 -0
  12. package/app/api/kanban/chat-history/[ticketId]/route.ts +36 -0
  13. package/app/api/memory/route.ts +12 -0
  14. package/app/api/transcribe/route.ts +37 -0
  15. package/app/api/tts/route.ts +42 -0
  16. package/app/chat/[id]/page.tsx +10 -0
  17. package/app/chat/page.tsx +200 -0
  18. package/app/crons/page.tsx +870 -0
  19. package/app/docs/page.tsx +399 -0
  20. package/app/favicon.ico +0 -0
  21. package/app/globals.css +692 -0
  22. package/app/kanban/page.tsx +327 -0
  23. package/app/layout.tsx +45 -0
  24. package/app/memory/page.tsx +685 -0
  25. package/app/page.tsx +817 -0
  26. package/app/providers.tsx +37 -0
  27. package/app/settings/page.tsx +901 -0
  28. package/app/settings-provider.tsx +209 -0
  29. package/components/AgentAvatar.tsx +54 -0
  30. package/components/AgentNode.tsx +122 -0
  31. package/components/Breadcrumbs.tsx +126 -0
  32. package/components/DynamicFavicon.tsx +62 -0
  33. package/components/ErrorState.tsx +97 -0
  34. package/components/FeedView.tsx +494 -0
  35. package/components/GlobalSearch.tsx +571 -0
  36. package/components/GridView.tsx +532 -0
  37. package/components/ManorMap.tsx +157 -0
  38. package/components/MobileSidebar.tsx +251 -0
  39. package/components/NavLinks.tsx +271 -0
  40. package/components/OnboardingWizard.tsx +1067 -0
  41. package/components/Sidebar.tsx +115 -0
  42. package/components/ThemeToggle.tsx +108 -0
  43. package/components/chat/AgentList.tsx +537 -0
  44. package/components/chat/ConversationView.tsx +1047 -0
  45. package/components/chat/FileAttachment.tsx +140 -0
  46. package/components/chat/MediaPreview.tsx +111 -0
  47. package/components/chat/VoiceMessage.tsx +139 -0
  48. package/components/crons/PipelineGraph.tsx +327 -0
  49. package/components/crons/WeeklySchedule.tsx +630 -0
  50. package/components/docs/AgentsSection.tsx +209 -0
  51. package/components/docs/ApiReferenceSection.tsx +256 -0
  52. package/components/docs/ArchitectureSection.tsx +221 -0
  53. package/components/docs/ComponentsSection.tsx +253 -0
  54. package/components/docs/CronSystemSection.tsx +235 -0
  55. package/components/docs/DocSection.tsx +346 -0
  56. package/components/docs/GettingStartedSection.tsx +169 -0
  57. package/components/docs/ThemingSection.tsx +257 -0
  58. package/components/docs/TroubleshootingSection.tsx +200 -0
  59. package/components/kanban/AgentPicker.tsx +321 -0
  60. package/components/kanban/CreateTicketModal.tsx +333 -0
  61. package/components/kanban/KanbanBoard.tsx +70 -0
  62. package/components/kanban/KanbanColumn.tsx +166 -0
  63. package/components/kanban/TicketCard.tsx +245 -0
  64. package/components/kanban/TicketDetailPanel.tsx +850 -0
  65. package/components/ui/badge.tsx +48 -0
  66. package/components/ui/button.tsx +64 -0
  67. package/components/ui/card.tsx +92 -0
  68. package/components/ui/dialog.tsx +158 -0
  69. package/components/ui/scroll-area.tsx +58 -0
  70. package/components/ui/separator.tsx +28 -0
  71. package/components/ui/skeleton.tsx +27 -0
  72. package/components/ui/tabs.tsx +91 -0
  73. package/components/ui/tooltip.tsx +57 -0
  74. package/components.json +23 -0
  75. package/docs/API.md +648 -0
  76. package/docs/COMPONENTS.md +1059 -0
  77. package/docs/THEMING.md +795 -0
  78. package/lib/agents-registry.ts +35 -0
  79. package/lib/agents.json +282 -0
  80. package/lib/agents.test.ts +367 -0
  81. package/lib/agents.ts +32 -0
  82. package/lib/anthropic.test.ts +422 -0
  83. package/lib/anthropic.ts +220 -0
  84. package/lib/api-error.ts +16 -0
  85. package/lib/audio-recorder.test.ts +72 -0
  86. package/lib/audio-recorder.ts +169 -0
  87. package/lib/conversations.test.ts +331 -0
  88. package/lib/conversations.ts +117 -0
  89. package/lib/cron-pipelines.test.ts +69 -0
  90. package/lib/cron-pipelines.ts +58 -0
  91. package/lib/cron-runs.test.ts +118 -0
  92. package/lib/cron-runs.ts +67 -0
  93. package/lib/cron-utils.test.ts +222 -0
  94. package/lib/cron-utils.ts +160 -0
  95. package/lib/crons.test.ts +502 -0
  96. package/lib/crons.ts +114 -0
  97. package/lib/env.test.ts +44 -0
  98. package/lib/env.ts +14 -0
  99. package/lib/kanban/automation.test.ts +245 -0
  100. package/lib/kanban/automation.ts +143 -0
  101. package/lib/kanban/chat-store.test.ts +149 -0
  102. package/lib/kanban/chat-store.ts +81 -0
  103. package/lib/kanban/store.test.ts +238 -0
  104. package/lib/kanban/store.ts +98 -0
  105. package/lib/kanban/types.ts +50 -0
  106. package/lib/kanban/useAgentWork.ts +78 -0
  107. package/lib/memory.ts +45 -0
  108. package/lib/multimodal.test.ts +219 -0
  109. package/lib/multimodal.ts +68 -0
  110. package/lib/pipeline.integration.test.ts +343 -0
  111. package/lib/sanitize.ts +194 -0
  112. package/lib/settings.test.ts +137 -0
  113. package/lib/settings.ts +94 -0
  114. package/lib/styles.ts +24 -0
  115. package/lib/themes.ts +9 -0
  116. package/lib/transcribe.test.ts +141 -0
  117. package/lib/transcribe.ts +111 -0
  118. package/lib/types.ts +66 -0
  119. package/lib/utils.ts +6 -0
  120. package/lib/validation.test.ts +132 -0
  121. package/lib/validation.ts +80 -0
  122. package/next.config.ts +7 -0
  123. package/package.json +56 -0
  124. package/postcss.config.mjs +7 -0
  125. package/public/file.svg +1 -0
  126. package/public/globe.svg +1 -0
  127. package/public/next.svg +1 -0
  128. package/public/vercel.svg +1 -0
  129. package/public/window.svg +1 -0
  130. package/scripts/setup.mjs +215 -0
  131. package/tsconfig.json +34 -0
  132. 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
+ ```