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
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Heading,
|
|
3
|
+
SubHeading,
|
|
4
|
+
Paragraph,
|
|
5
|
+
CodeBlock,
|
|
6
|
+
InlineCode,
|
|
7
|
+
Table,
|
|
8
|
+
BulletList,
|
|
9
|
+
NumberedList,
|
|
10
|
+
Callout,
|
|
11
|
+
} from "./DocSection";
|
|
12
|
+
|
|
13
|
+
export function AgentsSection() {
|
|
14
|
+
return (
|
|
15
|
+
<>
|
|
16
|
+
<Heading>Agents</Heading>
|
|
17
|
+
<Paragraph>
|
|
18
|
+
ClawPort ships with a default agent registry at{" "}
|
|
19
|
+
<InlineCode>lib/agents.json</InlineCode>. This is a working example
|
|
20
|
+
showing a full team hierarchy. It works out of the box if your OpenClaw
|
|
21
|
+
workspace has matching agent SOUL files.
|
|
22
|
+
</Paragraph>
|
|
23
|
+
|
|
24
|
+
<SubHeading>Using Your Own Agents</SubHeading>
|
|
25
|
+
<Paragraph>
|
|
26
|
+
To define your own agent team, create a file at:
|
|
27
|
+
</Paragraph>
|
|
28
|
+
<CodeBlock>{`$WORKSPACE_PATH/clawport/agents.json`}</CodeBlock>
|
|
29
|
+
<Paragraph>
|
|
30
|
+
ClawPort checks for this file on every request. If it exists, it
|
|
31
|
+
replaces the bundled registry entirely. If it's missing or contains
|
|
32
|
+
invalid JSON, the bundled default is used as a fallback.
|
|
33
|
+
</Paragraph>
|
|
34
|
+
|
|
35
|
+
<SubHeading>Agent Entry Format</SubHeading>
|
|
36
|
+
<CodeBlock title="agents.json">
|
|
37
|
+
{`[
|
|
38
|
+
{
|
|
39
|
+
"id": "my-agent",
|
|
40
|
+
"name": "My Agent",
|
|
41
|
+
"title": "What this agent does",
|
|
42
|
+
"reportsTo": null,
|
|
43
|
+
"directReports": [],
|
|
44
|
+
"soulPath": "agents/my-agent/SOUL.md",
|
|
45
|
+
"voiceId": null,
|
|
46
|
+
"color": "#06b6d4",
|
|
47
|
+
"emoji": "\u{1F916}",
|
|
48
|
+
"tools": ["read", "write"],
|
|
49
|
+
"memoryPath": null,
|
|
50
|
+
"description": "One-liner about this agent."
|
|
51
|
+
}
|
|
52
|
+
]`}
|
|
53
|
+
</CodeBlock>
|
|
54
|
+
|
|
55
|
+
<SubHeading>Field Reference</SubHeading>
|
|
56
|
+
<Table
|
|
57
|
+
headers={["Field", "Type", "Description"]}
|
|
58
|
+
rows={[
|
|
59
|
+
[
|
|
60
|
+
<InlineCode key="id">id</InlineCode>,
|
|
61
|
+
"string",
|
|
62
|
+
'Unique slug for the agent (e.g., "vera")',
|
|
63
|
+
],
|
|
64
|
+
[
|
|
65
|
+
<InlineCode key="name">name</InlineCode>,
|
|
66
|
+
"string",
|
|
67
|
+
'Display name (e.g., "VERA")',
|
|
68
|
+
],
|
|
69
|
+
[
|
|
70
|
+
<InlineCode key="title">title</InlineCode>,
|
|
71
|
+
"string",
|
|
72
|
+
'Role title (e.g., "Chief Strategy Officer")',
|
|
73
|
+
],
|
|
74
|
+
[
|
|
75
|
+
<InlineCode key="rt">reportsTo</InlineCode>,
|
|
76
|
+
"string | null",
|
|
77
|
+
"Parent agent id for the org chart. null for the root.",
|
|
78
|
+
],
|
|
79
|
+
[
|
|
80
|
+
<InlineCode key="dr">directReports</InlineCode>,
|
|
81
|
+
"string[]",
|
|
82
|
+
"Array of child agent ids",
|
|
83
|
+
],
|
|
84
|
+
[
|
|
85
|
+
<InlineCode key="sp">soulPath</InlineCode>,
|
|
86
|
+
"string | null",
|
|
87
|
+
"Path to the agent's SOUL.md, relative to WORKSPACE_PATH",
|
|
88
|
+
],
|
|
89
|
+
[
|
|
90
|
+
<InlineCode key="vi">voiceId</InlineCode>,
|
|
91
|
+
"string | null",
|
|
92
|
+
"ElevenLabs voice ID (requires ELEVENLABS_API_KEY)",
|
|
93
|
+
],
|
|
94
|
+
[
|
|
95
|
+
<InlineCode key="co">color</InlineCode>,
|
|
96
|
+
"string",
|
|
97
|
+
"Hex color for the agent's node in the Org Map",
|
|
98
|
+
],
|
|
99
|
+
[
|
|
100
|
+
<InlineCode key="em">emoji</InlineCode>,
|
|
101
|
+
"string",
|
|
102
|
+
"Emoji shown as the agent's avatar",
|
|
103
|
+
],
|
|
104
|
+
[
|
|
105
|
+
<InlineCode key="to">tools</InlineCode>,
|
|
106
|
+
"string[]",
|
|
107
|
+
"List of tools this agent has access to",
|
|
108
|
+
],
|
|
109
|
+
[
|
|
110
|
+
<InlineCode key="mp">memoryPath</InlineCode>,
|
|
111
|
+
"string | null",
|
|
112
|
+
"Path to agent-specific memory (relative to WORKSPACE_PATH)",
|
|
113
|
+
],
|
|
114
|
+
[
|
|
115
|
+
<InlineCode key="de">description</InlineCode>,
|
|
116
|
+
"string",
|
|
117
|
+
"One-line description shown in the UI",
|
|
118
|
+
],
|
|
119
|
+
]}
|
|
120
|
+
/>
|
|
121
|
+
|
|
122
|
+
<SubHeading>Hierarchy Rules</SubHeading>
|
|
123
|
+
<BulletList
|
|
124
|
+
items={[
|
|
125
|
+
<>
|
|
126
|
+
Exactly one agent should have{" "}
|
|
127
|
+
<InlineCode>{"\"reportsTo\": null"}</InlineCode> -- this is your
|
|
128
|
+
root/orchestrator node.
|
|
129
|
+
</>,
|
|
130
|
+
<>
|
|
131
|
+
<InlineCode>directReports</InlineCode> should be consistent with{" "}
|
|
132
|
+
<InlineCode>reportsTo</InlineCode>. If agent B reports to agent A,
|
|
133
|
+
then A's directReports should include B's id.
|
|
134
|
+
</>,
|
|
135
|
+
"The Org Map uses these relationships to build the org chart automatically.",
|
|
136
|
+
]}
|
|
137
|
+
/>
|
|
138
|
+
|
|
139
|
+
<SubHeading>Example: Minimal Two-Agent Setup</SubHeading>
|
|
140
|
+
<CodeBlock title="agents.json">
|
|
141
|
+
{`[
|
|
142
|
+
{
|
|
143
|
+
"id": "boss",
|
|
144
|
+
"name": "Boss",
|
|
145
|
+
"title": "Orchestrator",
|
|
146
|
+
"reportsTo": null,
|
|
147
|
+
"directReports": ["worker"],
|
|
148
|
+
"soulPath": "SOUL.md",
|
|
149
|
+
"voiceId": null,
|
|
150
|
+
"color": "#f5c518",
|
|
151
|
+
"emoji": "\u{1F451}",
|
|
152
|
+
"tools": ["read", "write", "exec", "message"],
|
|
153
|
+
"memoryPath": null,
|
|
154
|
+
"description": "Top-level orchestrator."
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"id": "worker",
|
|
158
|
+
"name": "Worker",
|
|
159
|
+
"title": "Task Runner",
|
|
160
|
+
"reportsTo": "boss",
|
|
161
|
+
"directReports": [],
|
|
162
|
+
"soulPath": "agents/worker/SOUL.md",
|
|
163
|
+
"voiceId": null,
|
|
164
|
+
"color": "#22c55e",
|
|
165
|
+
"emoji": "\u{2699}\u{FE0F}",
|
|
166
|
+
"tools": ["read", "write"],
|
|
167
|
+
"memoryPath": null,
|
|
168
|
+
"description": "Handles assigned tasks."
|
|
169
|
+
}
|
|
170
|
+
]`}
|
|
171
|
+
</CodeBlock>
|
|
172
|
+
|
|
173
|
+
<SubHeading>Registry Resolution</SubHeading>
|
|
174
|
+
<NumberedList
|
|
175
|
+
items={[
|
|
176
|
+
<>
|
|
177
|
+
<InlineCode>loadRegistry()</InlineCode> checks{" "}
|
|
178
|
+
<InlineCode>$WORKSPACE_PATH/clawport/agents.json</InlineCode> first
|
|
179
|
+
(user override).
|
|
180
|
+
</>,
|
|
181
|
+
<>
|
|
182
|
+
Falls back to bundled <InlineCode>lib/agents.json</InlineCode> if
|
|
183
|
+
the workspace file is missing or has invalid JSON.
|
|
184
|
+
</>,
|
|
185
|
+
<>
|
|
186
|
+
<InlineCode>lib/agents.ts</InlineCode> merges in SOUL.md content
|
|
187
|
+
from each agent's <InlineCode>soulPath</InlineCode>.
|
|
188
|
+
</>,
|
|
189
|
+
"The result is the full agent list used by all pages.",
|
|
190
|
+
]}
|
|
191
|
+
/>
|
|
192
|
+
|
|
193
|
+
<Callout type="tip">
|
|
194
|
+
You can add a new agent without editing any source code -- just update
|
|
195
|
+
your workspace <InlineCode>agents.json</InlineCode>. The agent will
|
|
196
|
+
automatically appear in the Org Map, Chat, and Detail pages.
|
|
197
|
+
</Callout>
|
|
198
|
+
|
|
199
|
+
<SubHeading>Agent Display Overrides</SubHeading>
|
|
200
|
+
<Paragraph>
|
|
201
|
+
Each agent can have per-agent emoji and/or profile image overrides via
|
|
202
|
+
the Settings page. These are stored in{" "}
|
|
203
|
+
<InlineCode>ClawPortSettings.agentOverrides</InlineCode> keyed by agent ID.
|
|
204
|
+
The <InlineCode>getAgentDisplay()</InlineCode> function resolves the
|
|
205
|
+
effective visual for each agent, considering overrides.
|
|
206
|
+
</Paragraph>
|
|
207
|
+
</>
|
|
208
|
+
);
|
|
209
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Heading,
|
|
3
|
+
SubHeading,
|
|
4
|
+
Paragraph,
|
|
5
|
+
CodeBlock,
|
|
6
|
+
InlineCode,
|
|
7
|
+
Table,
|
|
8
|
+
Callout,
|
|
9
|
+
InfoCard,
|
|
10
|
+
} from "./DocSection";
|
|
11
|
+
|
|
12
|
+
export function ApiReferenceSection() {
|
|
13
|
+
return (
|
|
14
|
+
<>
|
|
15
|
+
<Heading>API Reference</Heading>
|
|
16
|
+
<Paragraph>
|
|
17
|
+
All API routes are Next.js App Router route handlers under{" "}
|
|
18
|
+
<InlineCode>app/api/</InlineCode>. The base URL during development is{" "}
|
|
19
|
+
<InlineCode>http://localhost:3000</InlineCode>.
|
|
20
|
+
</Paragraph>
|
|
21
|
+
|
|
22
|
+
<InfoCard title="Error Format">
|
|
23
|
+
<Paragraph>
|
|
24
|
+
All error responses share a consistent JSON shape:
|
|
25
|
+
</Paragraph>
|
|
26
|
+
<CodeBlock>{`{ "error": "Human-readable error message" }`}</CodeBlock>
|
|
27
|
+
</InfoCard>
|
|
28
|
+
|
|
29
|
+
<SubHeading>Route Summary</SubHeading>
|
|
30
|
+
<Table
|
|
31
|
+
headers={["Method", "Endpoint", "Gateway", "Content-Type"]}
|
|
32
|
+
rows={[
|
|
33
|
+
[
|
|
34
|
+
"GET",
|
|
35
|
+
<InlineCode key="1">/api/agents</InlineCode>,
|
|
36
|
+
"No",
|
|
37
|
+
"application/json",
|
|
38
|
+
],
|
|
39
|
+
[
|
|
40
|
+
"POST",
|
|
41
|
+
<InlineCode key="2">/api/chat/[id]</InlineCode>,
|
|
42
|
+
"Yes",
|
|
43
|
+
"text/event-stream",
|
|
44
|
+
],
|
|
45
|
+
[
|
|
46
|
+
"GET",
|
|
47
|
+
<InlineCode key="3">/api/crons</InlineCode>,
|
|
48
|
+
"No",
|
|
49
|
+
"application/json",
|
|
50
|
+
],
|
|
51
|
+
[
|
|
52
|
+
"GET",
|
|
53
|
+
<InlineCode key="4">/api/cron-runs</InlineCode>,
|
|
54
|
+
"No",
|
|
55
|
+
"application/json",
|
|
56
|
+
],
|
|
57
|
+
[
|
|
58
|
+
"GET",
|
|
59
|
+
<InlineCode key="5">/api/memory</InlineCode>,
|
|
60
|
+
"No",
|
|
61
|
+
"application/json",
|
|
62
|
+
],
|
|
63
|
+
[
|
|
64
|
+
"POST",
|
|
65
|
+
<InlineCode key="6">/api/tts</InlineCode>,
|
|
66
|
+
"Yes",
|
|
67
|
+
"audio/mpeg",
|
|
68
|
+
],
|
|
69
|
+
[
|
|
70
|
+
"POST",
|
|
71
|
+
<InlineCode key="7">/api/transcribe</InlineCode>,
|
|
72
|
+
"Yes",
|
|
73
|
+
"application/json",
|
|
74
|
+
],
|
|
75
|
+
[
|
|
76
|
+
"POST",
|
|
77
|
+
<InlineCode key="8">/api/kanban/chat/[id]</InlineCode>,
|
|
78
|
+
"Yes",
|
|
79
|
+
"text/event-stream",
|
|
80
|
+
],
|
|
81
|
+
[
|
|
82
|
+
"GET",
|
|
83
|
+
<InlineCode key="9">/api/kanban/chat-history/[ticketId]</InlineCode>,
|
|
84
|
+
"No",
|
|
85
|
+
"application/json",
|
|
86
|
+
],
|
|
87
|
+
[
|
|
88
|
+
"POST",
|
|
89
|
+
<InlineCode key="10">/api/kanban/chat-history/[ticketId]</InlineCode>,
|
|
90
|
+
"No",
|
|
91
|
+
"application/json",
|
|
92
|
+
],
|
|
93
|
+
]}
|
|
94
|
+
/>
|
|
95
|
+
|
|
96
|
+
{/* ── GET /api/agents ────────────────────────────────────── */}
|
|
97
|
+
<SubHeading>GET /api/agents</SubHeading>
|
|
98
|
+
<Paragraph>
|
|
99
|
+
Returns the full list of registered agents, each with their SOUL.md
|
|
100
|
+
content loaded from the filesystem. No parameters required.
|
|
101
|
+
</Paragraph>
|
|
102
|
+
<Table
|
|
103
|
+
headers={["Field", "Type", "Description"]}
|
|
104
|
+
rows={[
|
|
105
|
+
[<InlineCode key="id">id</InlineCode>, "string", "Slug identifier"],
|
|
106
|
+
[<InlineCode key="n">name</InlineCode>, "string", "Display name"],
|
|
107
|
+
[<InlineCode key="t">title</InlineCode>, "string", "Role title"],
|
|
108
|
+
[
|
|
109
|
+
<InlineCode key="s">soul</InlineCode>,
|
|
110
|
+
"string | null",
|
|
111
|
+
"Full SOUL.md content, or null if file not found",
|
|
112
|
+
],
|
|
113
|
+
[
|
|
114
|
+
<InlineCode key="c">crons</InlineCode>,
|
|
115
|
+
"CronJob[]",
|
|
116
|
+
"Always [] from this endpoint (populated client-side)",
|
|
117
|
+
],
|
|
118
|
+
]}
|
|
119
|
+
/>
|
|
120
|
+
|
|
121
|
+
{/* ── POST /api/chat/[id] ────────────────────────────────── */}
|
|
122
|
+
<SubHeading>POST /api/chat/[id]</SubHeading>
|
|
123
|
+
<Paragraph>
|
|
124
|
+
Send a chat message to an agent and receive a streaming response. Has
|
|
125
|
+
two pipelines depending on whether the latest user message contains
|
|
126
|
+
images.
|
|
127
|
+
</Paragraph>
|
|
128
|
+
<Table
|
|
129
|
+
headers={["Field", "Type", "Required", "Description"]}
|
|
130
|
+
rows={[
|
|
131
|
+
[
|
|
132
|
+
<InlineCode key="m">messages</InlineCode>,
|
|
133
|
+
"ApiMessage[]",
|
|
134
|
+
"Yes",
|
|
135
|
+
"Conversation history",
|
|
136
|
+
],
|
|
137
|
+
[
|
|
138
|
+
<InlineCode key="o">operatorName</InlineCode>,
|
|
139
|
+
"string",
|
|
140
|
+
"No",
|
|
141
|
+
'Name shown to the agent. Defaults to "Operator"',
|
|
142
|
+
],
|
|
143
|
+
]}
|
|
144
|
+
/>
|
|
145
|
+
<Paragraph>
|
|
146
|
+
<strong style={{ color: "var(--text-primary)" }}>Pipeline 1 (Text):</strong>{" "}
|
|
147
|
+
Streaming chat completion via the gateway. Response is SSE with{" "}
|
|
148
|
+
<InlineCode>{"data: {\"content\":\"token\"}"}</InlineCode> frames.
|
|
149
|
+
</Paragraph>
|
|
150
|
+
<Paragraph>
|
|
151
|
+
<strong style={{ color: "var(--text-primary)" }}>Pipeline 2 (Vision):</strong>{" "}
|
|
152
|
+
When the latest message contains image_url content. Uses CLI chat.send +
|
|
153
|
+
chat.history polling. Complete response arrives in a single SSE frame.
|
|
154
|
+
</Paragraph>
|
|
155
|
+
|
|
156
|
+
{/* ── GET /api/crons ─────────────────────────────────────── */}
|
|
157
|
+
<SubHeading>GET /api/crons</SubHeading>
|
|
158
|
+
<Paragraph>
|
|
159
|
+
Returns all cron jobs registered with OpenClaw, enriched with schedule
|
|
160
|
+
descriptions, agent ownership, and delivery config. Runs{" "}
|
|
161
|
+
<InlineCode>openclaw cron list --json</InlineCode> via the CLI.
|
|
162
|
+
</Paragraph>
|
|
163
|
+
|
|
164
|
+
{/* ── GET /api/cron-runs ─────────────────────────────────── */}
|
|
165
|
+
<SubHeading>GET /api/cron-runs</SubHeading>
|
|
166
|
+
<Paragraph>
|
|
167
|
+
Returns cron run history parsed from JSONL log files on the filesystem.
|
|
168
|
+
Results sorted newest-first. Optional{" "}
|
|
169
|
+
<InlineCode>jobId</InlineCode> query parameter filters to a specific
|
|
170
|
+
job.
|
|
171
|
+
</Paragraph>
|
|
172
|
+
|
|
173
|
+
{/* ── GET /api/memory ────────────────────────────────────── */}
|
|
174
|
+
<SubHeading>GET /api/memory</SubHeading>
|
|
175
|
+
<Paragraph>
|
|
176
|
+
Returns the contents of key memory files from the workspace: long-term
|
|
177
|
+
memory, team memory, team intel, and the daily logs for today and
|
|
178
|
+
yesterday. Only files that exist are included in the response.
|
|
179
|
+
</Paragraph>
|
|
180
|
+
|
|
181
|
+
{/* ── POST /api/tts ──────────────────────────────────────── */}
|
|
182
|
+
<SubHeading>POST /api/tts</SubHeading>
|
|
183
|
+
<Paragraph>
|
|
184
|
+
Converts text to speech audio using the OpenClaw gateway's TTS endpoint.
|
|
185
|
+
</Paragraph>
|
|
186
|
+
<Table
|
|
187
|
+
headers={["Field", "Type", "Required", "Description"]}
|
|
188
|
+
rows={[
|
|
189
|
+
[
|
|
190
|
+
<InlineCode key="t">text</InlineCode>,
|
|
191
|
+
"string",
|
|
192
|
+
"Yes",
|
|
193
|
+
"The text to synthesize",
|
|
194
|
+
],
|
|
195
|
+
[
|
|
196
|
+
<InlineCode key="v">voice</InlineCode>,
|
|
197
|
+
"string",
|
|
198
|
+
"No",
|
|
199
|
+
'Voice identifier. Defaults to "alloy"',
|
|
200
|
+
],
|
|
201
|
+
]}
|
|
202
|
+
/>
|
|
203
|
+
|
|
204
|
+
{/* ── POST /api/transcribe ───────────────────────────────── */}
|
|
205
|
+
<SubHeading>POST /api/transcribe</SubHeading>
|
|
206
|
+
<Paragraph>
|
|
207
|
+
Transcribes audio to text using the Whisper endpoint. Request body is
|
|
208
|
+
multipart form data with an <InlineCode>audio</InlineCode> file field.
|
|
209
|
+
</Paragraph>
|
|
210
|
+
|
|
211
|
+
{/* ── SSE Protocol ───────────────────────────────────────── */}
|
|
212
|
+
<SubHeading>SSE Stream Protocol</SubHeading>
|
|
213
|
+
<Paragraph>
|
|
214
|
+
All streaming chat endpoints use the same Server-Sent Events protocol:
|
|
215
|
+
</Paragraph>
|
|
216
|
+
<CodeBlock>
|
|
217
|
+
{`data: {"content":"Hello"}
|
|
218
|
+
|
|
219
|
+
data: {"content":" there"}
|
|
220
|
+
|
|
221
|
+
data: [DONE]`}
|
|
222
|
+
</CodeBlock>
|
|
223
|
+
<Callout type="note">
|
|
224
|
+
Content-Type is <InlineCode>text/event-stream</InlineCode> with{" "}
|
|
225
|
+
<InlineCode>Cache-Control: no-cache</InlineCode>. If a stream error
|
|
226
|
+
occurs mid-response, the server sends [DONE] and closes the connection.
|
|
227
|
+
</Callout>
|
|
228
|
+
|
|
229
|
+
<SubHeading>Client-Side Consumption</SubHeading>
|
|
230
|
+
<CodeBlock title="example">
|
|
231
|
+
{`const reader = response.body.getReader()
|
|
232
|
+
const decoder = new TextDecoder()
|
|
233
|
+
let fullText = ''
|
|
234
|
+
|
|
235
|
+
while (true) {
|
|
236
|
+
const { done, value } = await reader.read()
|
|
237
|
+
if (done) break
|
|
238
|
+
|
|
239
|
+
const chunk = decoder.decode(value, { stream: true })
|
|
240
|
+
const lines = chunk.split('\\n')
|
|
241
|
+
|
|
242
|
+
for (const line of lines) {
|
|
243
|
+
if (line.startsWith('data: ')) {
|
|
244
|
+
const payload = line.slice(6)
|
|
245
|
+
if (payload === '[DONE]') return fullText
|
|
246
|
+
try {
|
|
247
|
+
const { content } = JSON.parse(payload)
|
|
248
|
+
fullText += content
|
|
249
|
+
} catch { /* skip malformed frames */ }
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}`}
|
|
253
|
+
</CodeBlock>
|
|
254
|
+
</>
|
|
255
|
+
);
|
|
256
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Heading,
|
|
3
|
+
SubHeading,
|
|
4
|
+
Paragraph,
|
|
5
|
+
CodeBlock,
|
|
6
|
+
InlineCode,
|
|
7
|
+
Table,
|
|
8
|
+
BulletList,
|
|
9
|
+
Callout,
|
|
10
|
+
InfoCard,
|
|
11
|
+
} from "./DocSection";
|
|
12
|
+
|
|
13
|
+
export function ArchitectureSection() {
|
|
14
|
+
return (
|
|
15
|
+
<>
|
|
16
|
+
<Heading>Architecture</Heading>
|
|
17
|
+
<Paragraph>
|
|
18
|
+
ClawPort is a Next.js 16 dashboard for managing OpenClaw AI agents. It
|
|
19
|
+
provides an org chart (Org Map), direct agent chat with multimodal
|
|
20
|
+
support, cron monitoring, kanban task board, and memory browsing. All AI
|
|
21
|
+
calls route through the OpenClaw gateway -- no separate API keys needed.
|
|
22
|
+
</Paragraph>
|
|
23
|
+
|
|
24
|
+
<SubHeading>Tech Stack</SubHeading>
|
|
25
|
+
<BulletList
|
|
26
|
+
items={[
|
|
27
|
+
"Next.js 16.1.6 (App Router, Turbopack)",
|
|
28
|
+
"React 19.2.3, TypeScript 5",
|
|
29
|
+
"Tailwind CSS 4 with CSS custom properties for theming",
|
|
30
|
+
"Vitest 4 with jsdom environment (17 suites, 288 tests)",
|
|
31
|
+
"OpenAI SDK (routed to Claude via OpenClaw gateway at localhost:18789)",
|
|
32
|
+
"React Flow (@xyflow/react) for org chart",
|
|
33
|
+
]}
|
|
34
|
+
/>
|
|
35
|
+
|
|
36
|
+
<SubHeading>Agent Registry Resolution</SubHeading>
|
|
37
|
+
<CodeBlock>
|
|
38
|
+
{`loadRegistry() checks:
|
|
39
|
+
1. $WORKSPACE_PATH/clawport/agents.json (user override)
|
|
40
|
+
2. Bundled lib/agents.json (default)`}
|
|
41
|
+
</CodeBlock>
|
|
42
|
+
<Paragraph>
|
|
43
|
+
<InlineCode>lib/agents-registry.ts</InlineCode> exports{" "}
|
|
44
|
+
<InlineCode>loadRegistry()</InlineCode>.{" "}
|
|
45
|
+
<InlineCode>lib/agents.ts</InlineCode> calls it to build the full agent
|
|
46
|
+
list (merging in SOUL.md content from the workspace). Users customize
|
|
47
|
+
their agent team by dropping an{" "}
|
|
48
|
+
<InlineCode>agents.json</InlineCode> into their workspace -- no source
|
|
49
|
+
edits needed.
|
|
50
|
+
</Paragraph>
|
|
51
|
+
|
|
52
|
+
<SubHeading>Chat Pipeline (Text)</SubHeading>
|
|
53
|
+
<CodeBlock>
|
|
54
|
+
{`Client -> POST /api/chat/[id] -> OpenAI SDK -> localhost:18789/v1/chat/completions -> Claude
|
|
55
|
+
(streaming SSE response)`}
|
|
56
|
+
</CodeBlock>
|
|
57
|
+
|
|
58
|
+
<SubHeading>Chat Pipeline (Images/Vision)</SubHeading>
|
|
59
|
+
<Paragraph>
|
|
60
|
+
The gateway's HTTP endpoint strips image_url content. Vision uses the
|
|
61
|
+
CLI agent pipeline:
|
|
62
|
+
</Paragraph>
|
|
63
|
+
<CodeBlock>
|
|
64
|
+
{`Client resizes image to 1200px max (Canvas API)
|
|
65
|
+
-> base64 data URL in message
|
|
66
|
+
-> POST /api/chat/[id]
|
|
67
|
+
-> Detects image in LATEST user message only (not history)
|
|
68
|
+
-> execFile: openclaw gateway call chat.send --params <json> --token <token>
|
|
69
|
+
-> Polls: openclaw gateway call chat.history every 2s
|
|
70
|
+
-> Matches response by timestamp >= sendTs
|
|
71
|
+
-> Returns assistant text via SSE`}
|
|
72
|
+
</CodeBlock>
|
|
73
|
+
|
|
74
|
+
<InfoCard title="Design Decisions">
|
|
75
|
+
<BulletList
|
|
76
|
+
items={[
|
|
77
|
+
<>
|
|
78
|
+
<strong style={{ color: "var(--text-primary)" }}>
|
|
79
|
+
Why send-then-poll?
|
|
80
|
+
</strong>{" "}
|
|
81
|
+
chat.send is async -- it returns immediately. We poll chat.history
|
|
82
|
+
until the assistant's response appears.
|
|
83
|
+
</>,
|
|
84
|
+
<>
|
|
85
|
+
<strong style={{ color: "var(--text-primary)" }}>
|
|
86
|
+
Why CLI and not WebSocket?
|
|
87
|
+
</strong>{" "}
|
|
88
|
+
The gateway WebSocket requires device keypair signing for
|
|
89
|
+
operator.write scope. The CLI has the device keys; custom clients
|
|
90
|
+
don't.
|
|
91
|
+
</>,
|
|
92
|
+
<>
|
|
93
|
+
<strong style={{ color: "var(--text-primary)" }}>
|
|
94
|
+
Why resize to 1200px?
|
|
95
|
+
</strong>{" "}
|
|
96
|
+
macOS ARG_MAX is 1MB. Unresized photos can produce multi-MB base64
|
|
97
|
+
that exceeds CLI argument limits (E2BIG error).
|
|
98
|
+
</>,
|
|
99
|
+
]}
|
|
100
|
+
/>
|
|
101
|
+
</InfoCard>
|
|
102
|
+
|
|
103
|
+
<SubHeading>Voice Message Pipeline</SubHeading>
|
|
104
|
+
<CodeBlock>
|
|
105
|
+
{`Browser MediaRecorder (webm/opus or mp4)
|
|
106
|
+
-> AudioContext AnalyserNode captures waveform (40-60 samples)
|
|
107
|
+
-> Stop -> audioBlob + waveform data
|
|
108
|
+
-> POST /api/transcribe (Whisper via gateway)
|
|
109
|
+
-> Transcription text sent as message content
|
|
110
|
+
-> Audio data URL + waveform stored in message for playback`}
|
|
111
|
+
</CodeBlock>
|
|
112
|
+
|
|
113
|
+
<SubHeading>operatorName Flow</SubHeading>
|
|
114
|
+
<CodeBlock>
|
|
115
|
+
{`OnboardingWizard / Settings page
|
|
116
|
+
-> ClawPortSettings.operatorName (localStorage)
|
|
117
|
+
-> settings-provider.tsx (React context)
|
|
118
|
+
-> NavLinks.tsx (dynamic initials + display name)
|
|
119
|
+
-> ConversationView.tsx (sends operatorName in POST body)
|
|
120
|
+
-> /api/chat/[id] route (injects into system prompt)`}
|
|
121
|
+
</CodeBlock>
|
|
122
|
+
<Callout type="note">
|
|
123
|
+
No hardcoded operator names anywhere. Falls back to "Operator" / "??"
|
|
124
|
+
when unset.
|
|
125
|
+
</Callout>
|
|
126
|
+
|
|
127
|
+
<SubHeading>Directory Structure</SubHeading>
|
|
128
|
+
<CodeBlock>
|
|
129
|
+
{`app/
|
|
130
|
+
page.tsx -- Org Map (React Flow org chart)
|
|
131
|
+
chat/page.tsx -- Multi-agent messenger
|
|
132
|
+
agents/[id]/page.tsx -- Agent detail profile
|
|
133
|
+
kanban/page.tsx -- Task board
|
|
134
|
+
crons/page.tsx -- Cron job monitor
|
|
135
|
+
memory/page.tsx -- Memory file browser
|
|
136
|
+
settings/page.tsx -- ClawPort personalization
|
|
137
|
+
docs/page.tsx -- Documentation browser
|
|
138
|
+
api/
|
|
139
|
+
agents/route.ts -- GET agents from registry
|
|
140
|
+
chat/[id]/route.ts -- POST chat (text + vision)
|
|
141
|
+
crons/route.ts -- GET crons via CLI
|
|
142
|
+
memory/route.ts -- GET memory files
|
|
143
|
+
tts/route.ts -- POST text-to-speech
|
|
144
|
+
transcribe/route.ts -- POST audio transcription
|
|
145
|
+
|
|
146
|
+
components/
|
|
147
|
+
OrgMap.tsx -- React Flow graph with auto-layout
|
|
148
|
+
AgentNode.tsx -- Custom node for the org chart
|
|
149
|
+
Sidebar.tsx -- Desktop navigation sidebar
|
|
150
|
+
MobileSidebar.tsx -- Mobile hamburger menu
|
|
151
|
+
NavLinks.tsx -- Sidebar nav links
|
|
152
|
+
ThemeToggle.tsx -- Theme switcher (5 themes)
|
|
153
|
+
GlobalSearch.tsx -- Cmd+K agent search
|
|
154
|
+
chat/ -- Chat components
|
|
155
|
+
kanban/ -- Kanban components
|
|
156
|
+
crons/ -- Cron components
|
|
157
|
+
docs/ -- Documentation components
|
|
158
|
+
|
|
159
|
+
lib/
|
|
160
|
+
agents.ts -- Agent registry + SOUL.md reader
|
|
161
|
+
agents-registry.ts -- Registry loader
|
|
162
|
+
anthropic.ts -- Vision pipeline (send + poll)
|
|
163
|
+
conversations.ts -- Conversation store (localStorage)
|
|
164
|
+
settings.ts -- ClawPortSettings type + persistence
|
|
165
|
+
themes.ts -- Theme definitions
|
|
166
|
+
types.ts -- Shared TypeScript types`}
|
|
167
|
+
</CodeBlock>
|
|
168
|
+
|
|
169
|
+
<SubHeading>Key Libraries</SubHeading>
|
|
170
|
+
<Table
|
|
171
|
+
headers={["File", "Purpose"]}
|
|
172
|
+
rows={[
|
|
173
|
+
[
|
|
174
|
+
<InlineCode key="a">lib/agents.ts</InlineCode>,
|
|
175
|
+
"Agent list builder -- calls loadRegistry(), merges SOUL.md",
|
|
176
|
+
],
|
|
177
|
+
[
|
|
178
|
+
<InlineCode key="ar">lib/agents-registry.ts</InlineCode>,
|
|
179
|
+
"loadRegistry() -- workspace override -> bundled fallback",
|
|
180
|
+
],
|
|
181
|
+
[
|
|
182
|
+
<InlineCode key="an">lib/anthropic.ts</InlineCode>,
|
|
183
|
+
"Vision pipeline: hasImageContent, sendViaOpenClaw (send + poll), execCli",
|
|
184
|
+
],
|
|
185
|
+
[
|
|
186
|
+
<InlineCode key="c">lib/conversations.ts</InlineCode>,
|
|
187
|
+
"Conversation store with localStorage persistence",
|
|
188
|
+
],
|
|
189
|
+
[
|
|
190
|
+
<InlineCode key="e">lib/env.ts</InlineCode>,
|
|
191
|
+
"requireEnv(name) -- safe env var access with clear errors",
|
|
192
|
+
],
|
|
193
|
+
[
|
|
194
|
+
<InlineCode key="m">lib/multimodal.ts</InlineCode>,
|
|
195
|
+
"buildApiContent() -- converts Message+Media to OpenAI API format",
|
|
196
|
+
],
|
|
197
|
+
[
|
|
198
|
+
<InlineCode key="s">lib/settings.ts</InlineCode>,
|
|
199
|
+
"ClawPortSettings type, loadSettings(), saveSettings() (localStorage)",
|
|
200
|
+
],
|
|
201
|
+
[
|
|
202
|
+
<InlineCode key="v">lib/validation.ts</InlineCode>,
|
|
203
|
+
"validateChatMessages() -- validates text + multimodal content arrays",
|
|
204
|
+
],
|
|
205
|
+
]}
|
|
206
|
+
/>
|
|
207
|
+
|
|
208
|
+
<SubHeading>Conventions</SubHeading>
|
|
209
|
+
<BulletList
|
|
210
|
+
items={[
|
|
211
|
+
"No external charting/media libraries -- native Web APIs (Canvas, MediaRecorder, AudioContext)",
|
|
212
|
+
"Base64 data URLs for all persisted media (not blob URLs)",
|
|
213
|
+
"CSS custom properties for theming -- no Tailwind color classes directly",
|
|
214
|
+
"Inline styles referencing CSS vars (e.g., style={{ color: 'var(--text-primary)' }})",
|
|
215
|
+
"Tests colocated with source: lib/foo.ts + lib/foo.test.ts",
|
|
216
|
+
"Call requireEnv() inside functions, not at module top level",
|
|
217
|
+
]}
|
|
218
|
+
/>
|
|
219
|
+
</>
|
|
220
|
+
);
|
|
221
|
+
}
|