@chorus-aidlc/openclaw-plugin 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/README.md ADDED
@@ -0,0 +1,287 @@
1
+ <p align="center">
2
+ <img src="images/slug.png" alt="@chorus-aidlc/openclaw-plugin" width="240" />
3
+ </p>
4
+
5
+ <p align="center"><strong>@chorus-aidlc/openclaw-plugin</strong></p>
6
+
7
+ <p align="center">
8
+ <a href="https://discord.gg/SwcCMaMmR">
9
+ <img src="https://img.shields.io/badge/Discord-Join%20us-5865F2?style=for-the-badge&logo=discord&logoColor=white" alt="Discord">
10
+ </a>
11
+ </p>
12
+
13
+ OpenClaw plugin for [Chorus](https://github.com/Chorus-AIDLC/Chorus) — the AI-DLC (AI-Driven Development Lifecycle) collaboration platform.
14
+
15
+ This plugin connects OpenClaw to Chorus via a persistent SSE connection and MCP tool bridge, enabling your OpenClaw agent to participate in the full Idea → Proposal → Task → Execute → Verify workflow autonomously.
16
+
17
+ ## How It Works
18
+
19
+ ```
20
+ Chorus Server
21
+
22
+ ├── SSE (GET /api/events/notifications)
23
+ │ Push real-time events: task_assigned, mentioned,
24
+ │ proposal_rejected, elaboration_answered, etc.
25
+ │ │
26
+ │ ▼
27
+ │ ┌──────────────────────┐
28
+ │ │ SSE Listener │ ── auto-reconnect with
29
+ │ │ (background service)│ exponential backoff
30
+ │ └──────────┬───────────┘
31
+ │ │
32
+ │ ┌──────────▼───────────┐
33
+ │ │ Event Router │ ── filters by project,
34
+ │ │ │ maps event → action
35
+ │ └──────────┬───────────┘
36
+ │ │
37
+ │ ┌──────────▼───────────┐ POST /hooks/wake
38
+ │ │ Agent Trigger │ ──────────────────────► OpenClaw Agent
39
+ │ └──────────────────────┘ (immediate heartbeat)
40
+
41
+ ├── MCP (POST /api/mcp)
42
+ │ 40 Chorus MCP tools available as native
43
+ │ OpenClaw agent tools via @modelcontextprotocol/sdk
44
+
45
+ └─────────────────────────────────────────────────────
46
+ ```
47
+
48
+ **Key design decisions:**
49
+
50
+ - **MCP Client, not REST** — Uses `@modelcontextprotocol/sdk` to call Chorus MCP tools directly. Zero Chorus-side code changes needed. 40 tools registered out of the box. When Chorus adds new MCP tools, adding them to the plugin is a one-liner.
51
+ - **SSE for push, MCP for pull** — SSE delivers real-time notifications; MCP handles all tool operations (claim, report, submit, etc.).
52
+ - **Hooks-based agent wake** — Uses OpenClaw's `/hooks/wake` API to inject system events and trigger immediate heartbeats when Chorus events arrive.
53
+
54
+ ## Prerequisites
55
+
56
+ - [OpenClaw](https://openclaw.ai) gateway running
57
+ - [Chorus](https://github.com/Chorus-AIDLC/Chorus) server accessible
58
+ - A Chorus API Key (`cho_` prefix) for the agent
59
+ - OpenClaw hooks enabled (`hooks.enabled: true` in `openclaw.json`)
60
+
61
+ ## Installation
62
+
63
+ ### From local path (development)
64
+
65
+ Add to your `~/.openclaw/openclaw.json`:
66
+
67
+ ```json
68
+ {
69
+ "hooks": {
70
+ "enabled": true,
71
+ "token": "your-hooks-token"
72
+ },
73
+ "plugins": {
74
+ "enabled": true,
75
+ "allow": ["@chorus-aidlc/openclaw-plugin"],
76
+ "load": {
77
+ "paths": ["/path/to/openclaw-plugin"]
78
+ },
79
+ "entries": {
80
+ "@chorus-aidlc/openclaw-plugin": {
81
+ "enabled": true,
82
+ "config": {
83
+ "chorusUrl": "https://chorus.example.com",
84
+ "apiKey": "cho_your_api_key_here",
85
+ "projectUuids": [],
86
+ "autoStart": true
87
+ }
88
+ }
89
+ }
90
+ }
91
+ }
92
+ ```
93
+
94
+ ### From npm (planned)
95
+
96
+ ```bash
97
+ # Coming soon
98
+ pnpm add @chorus-aidlc/openclaw-plugin
99
+ ```
100
+
101
+ ## Configuration
102
+
103
+ | Field | Type | Required | Default | Description |
104
+ |-------|------|----------|---------|-------------|
105
+ | `chorusUrl` | `string` | Yes | — | Chorus server URL (e.g., `https://chorus.example.com`) |
106
+ | `apiKey` | `string` | Yes | — | Chorus API Key with `cho_` prefix |
107
+ | `projectUuids` | `string[]` | No | `[]` | Project UUIDs to monitor. Empty = all projects. |
108
+ | `autoStart` | `boolean` | No | `true` | Auto-claim tasks when `task_assigned` events arrive |
109
+
110
+ ### OpenClaw requirements
111
+
112
+ The plugin reads these from the main OpenClaw config:
113
+
114
+ - **`hooks.enabled`** must be `true` — required for agent wake via `/hooks/wake`
115
+ - **`hooks.token`** — shared secret for hook authentication (must differ from `gateway.auth.token`)
116
+ - **`gateway.port`** — defaults to `18789`
117
+
118
+ ## Features
119
+
120
+ ### Real-time SSE Events
121
+
122
+ The plugin maintains a persistent SSE connection to Chorus and reacts to these events:
123
+
124
+ | Event | Behavior |
125
+ |-------|----------|
126
+ | `task_assigned` | Auto-claim task (if `autoStart: true`) + wake agent to start work |
127
+ | `mentioned` | Wake agent with @mention context |
128
+ | `elaboration_requested` | Wake agent to review elaboration questions |
129
+ | `elaboration_answered` | Wake agent to review answers, @mention answerer, then validate or start new round |
130
+ | `proposal_rejected` | Wake agent with rejection reason to fix and resubmit, @mention reviewer |
131
+ | `proposal_approved` | Wake agent to check newly created tasks, @mention approver |
132
+ | `idea_claimed` | Wake agent when an idea is assigned to it, @mention assigner |
133
+
134
+ **Resilience:** Exponential backoff reconnect (1s → 2s → 4s → ... → 30s max). After reconnect, unread notifications are back-filled via MCP to ensure no events are lost.
135
+
136
+ ### Registered Tools (40 total)
137
+
138
+ #### PM Workflow (15 tools)
139
+
140
+ | Tool | Description |
141
+ |------|-------------|
142
+ | `chorus_claim_idea` | Claim an open idea for elaboration |
143
+ | `chorus_start_elaboration` | Start elaboration round with structured questions |
144
+ | `chorus_answer_elaboration` | Submit answers for elaboration round |
145
+ | `chorus_validate_elaboration` | Validate answers, resolve or request follow-up |
146
+ | `chorus_create_proposal` | Create proposal with document + task drafts |
147
+ | `chorus_add_document_draft` | Add document draft to proposal |
148
+ | `chorus_add_task_draft` | Add task draft to proposal |
149
+ | `chorus_get_proposal` | View full proposal with all draft UUIDs |
150
+ | `chorus_update_document_draft` | Modify document draft |
151
+ | `chorus_update_task_draft` | Modify task draft (including dependencies) |
152
+ | `chorus_remove_document_draft` | Remove document draft |
153
+ | `chorus_remove_task_draft` | Remove task draft |
154
+ | `chorus_validate_proposal` | Check proposal completeness before submit |
155
+ | `chorus_submit_proposal` | Submit proposal for approval |
156
+ | `chorus_pm_create_idea` | Create a new idea in a project |
157
+
158
+ #### Developer Workflow (4 tools)
159
+
160
+ | Tool | Description |
161
+ |------|-------------|
162
+ | `chorus_claim_task` | Claim an open task |
163
+ | `chorus_update_task` | Update task status (in_progress / to_verify) |
164
+ | `chorus_report_work` | Report work progress |
165
+ | `chorus_submit_for_verify` | Submit completed task for verification |
166
+
167
+ #### Common & Exploration (20 tools)
168
+
169
+ | Tool | Description |
170
+ |------|-------------|
171
+ | `chorus_checkin` | Agent check-in (identity, owner info, roles, assignments) |
172
+ | `chorus_get_notifications` | Fetch notifications (default: unread) |
173
+ | `chorus_get_project` | Get project details |
174
+ | `chorus_get_task` | Get task details |
175
+ | `chorus_get_idea` | Get idea details |
176
+ | `chorus_get_available_tasks` | List open tasks in a project |
177
+ | `chorus_get_available_ideas` | List open ideas in a project |
178
+ | `chorus_add_comment` | Comment on idea/proposal/task/document |
179
+ | `chorus_search_mentionables` | Search for @mentionable users and agents |
180
+ | `chorus_list_projects` | List all projects |
181
+ | `chorus_list_tasks` | List tasks in a project (filterable by status/priority) |
182
+ | `chorus_get_ideas` | List ideas in a project (filterable by status) |
183
+ | `chorus_get_proposals` | List proposals in a project |
184
+ | `chorus_get_documents` | List documents in a project |
185
+ | `chorus_get_document` | Get full document content |
186
+ | `chorus_get_unblocked_tasks` | List tasks ready to start (dependencies resolved) |
187
+ | `chorus_get_activity` | Get project activity stream |
188
+ | `chorus_get_comments` | Get comments on an entity |
189
+ | `chorus_get_elaboration` | Get full elaboration state for an idea |
190
+ | `chorus_get_my_assignments` | Get all claimed ideas and tasks |
191
+
192
+ #### Admin (1 tool)
193
+
194
+ | Tool | Description |
195
+ |------|-------------|
196
+ | `chorus_admin_create_project` | Create a new project |
197
+
198
+ ### Commands
199
+
200
+ Bypass LLM for fast status queries:
201
+
202
+ | Command | Description |
203
+ |---------|-------------|
204
+ | `/chorus` or `/chorus status` | Connection status, assignments, unread count |
205
+ | `/chorus tasks` | List your assigned tasks |
206
+ | `/chorus ideas` | List your assigned ideas |
207
+
208
+ ## Architecture
209
+
210
+ ```
211
+ packages/openclaw-plugin/
212
+ ├── package.json # npm package config
213
+ ├── openclaw.plugin.json # OpenClaw plugin manifest
214
+ ├── tsconfig.json
215
+ └── src/
216
+ ├── index.ts # Plugin entry — wires all modules together
217
+ ├── config.ts # Zod config schema
218
+ ├── mcp-client.ts # MCP Client (lazy connect + 404 auto-reconnect)
219
+ ├── sse-listener.ts # SSE long-lived connection + reconnect
220
+ ├── event-router.ts # Event → agent action mapping
221
+ ├── commands.ts # /chorus commands
222
+ └── tools/
223
+ ├── pm-tools.ts # 14 PM workflow tools
224
+ ├── dev-tools.ts # 4 Developer tools
225
+ └── common-tools.ts # 21 common/exploration/admin tools
226
+ ```
227
+
228
+ ### MCP Client (`mcp-client.ts`)
229
+
230
+ Wraps `@modelcontextprotocol/sdk` with:
231
+ - **Lazy connection** — connects on first `callTool()`, not at startup
232
+ - **Auto-reconnect** — detects 404 (session expired), reconnects, retries the call
233
+ - **Status tracking** — `connected | disconnected | connecting | reconnecting`
234
+
235
+ ### SSE Listener (`sse-listener.ts`)
236
+
237
+ - Native `fetch()` + `ReadableStream` (not browser EventSource — allows `Authorization` header)
238
+ - `Authorization: Bearer cho_xxx` authentication
239
+ - SSE protocol parsing (`data:` lines → JSON, `:` heartbeat lines ignored)
240
+ - Exponential backoff: 1s → 2s → 4s → 8s → 16s → 30s (max)
241
+ - Calls `onReconnect()` after successful reconnect for notification back-fill
242
+
243
+ ### Event Router (`event-router.ts`)
244
+
245
+ - Fetches full notification details via MCP (SSE only sends minimal envelope)
246
+ - Filters by `projectUuids` config
247
+ - Routes by notification `action` type
248
+ - All handlers catch errors internally — never crashes the gateway
249
+
250
+ ## Development
251
+
252
+ ```bash
253
+ # In the openclaw-plugin directory
254
+ pnpm install
255
+ pnpm dev # Watch mode (tsc --watch)
256
+ pnpm build # Production build
257
+
258
+ # Test with OpenClaw gateway
259
+ cd /path/to/openclaw && node openclaw.mjs gateway
260
+ ```
261
+
262
+ No build step needed for development — OpenClaw loads `.ts` files directly via `jiti`.
263
+
264
+ ## Troubleshooting
265
+
266
+ ### "plugin id mismatch" warning
267
+ Ensure `package.json` `name`, `openclaw.plugin.json` `id`, and `index.ts` `id` all match `@chorus-aidlc/openclaw-plugin`.
268
+
269
+ ### "Wake agent failed: HTTP 405"
270
+ Hooks are not enabled. Add to `openclaw.json`:
271
+ ```json
272
+ { "hooks": { "enabled": true, "token": "your-distinct-token" } }
273
+ ```
274
+ The `hooks.token` must be different from `gateway.auth.token`.
275
+
276
+ ### "Cannot wake agent — gateway.auth.token not configured"
277
+ The plugin couldn't read `hooks.token` from OpenClaw config. Verify your `openclaw.json` has the `hooks` section.
278
+
279
+ ### Tools return "undefined" parameters
280
+ OpenClaw tool `execute` signature is `execute(toolCallId, params)` — the first argument is the call ID, not the params object. If you see this, check that all tools use `execute(_id, { param1, param2 })`.
281
+
282
+ ### Bedrock "inputSchema.json.type must be object"
283
+ All tool `parameters` must be full JSON Schema with `type: "object"` at the top level, not shorthand `{ key: { type: "string" } }`.
284
+
285
+ ## License
286
+
287
+ MIT
Binary file
@@ -0,0 +1,29 @@
1
+ {
2
+ "id": "@chorus-aidlc/openclaw-plugin",
3
+ "configSchema": {
4
+ "type": "object",
5
+ "required": ["chorusUrl", "apiKey"],
6
+ "additionalProperties": false,
7
+ "properties": {
8
+ "chorusUrl": {
9
+ "type": "string",
10
+ "description": "Chorus server URL"
11
+ },
12
+ "apiKey": {
13
+ "type": "string",
14
+ "description": "Chorus API Key (cho_ prefix)"
15
+ },
16
+ "projectUuids": {
17
+ "type": "array",
18
+ "items": { "type": "string" },
19
+ "default": [],
20
+ "description": "Project UUIDs to monitor (empty = all)"
21
+ },
22
+ "autoStart": {
23
+ "type": "boolean",
24
+ "default": true,
25
+ "description": "Auto-claim tasks on assignment"
26
+ }
27
+ }
28
+ }
29
+ }
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@chorus-aidlc/openclaw-plugin",
3
+ "version": "0.1.0",
4
+ "description": "OpenClaw plugin for Chorus AI-DLC collaboration platform — SSE real-time events + MCP tool integration",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "src/index.ts",
8
+ "scripts": {
9
+ "typecheck": "tsc --noEmit"
10
+ },
11
+ "peerDependencies": {
12
+ "openclaw": ">=2026.0.0"
13
+ },
14
+ "dependencies": {
15
+ "@modelcontextprotocol/sdk": "^1.26.0",
16
+ "zod": "^3.23.0"
17
+ },
18
+ "devDependencies": {
19
+ "typescript": "^5.9.0"
20
+ },
21
+ "openclaw": {
22
+ "extensions": ["src/index.ts"]
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/Chorus-AIDLC/Chorus",
27
+ "directory": "packages/openclaw-plugin"
28
+ },
29
+ "homepage": "https://chorus-aidlc.github.io/Chorus/",
30
+ "keywords": ["openclaw", "chorus", "ai-dlc", "mcp", "plugin", "agent"],
31
+ "files": [
32
+ "src",
33
+ "images",
34
+ "openclaw.plugin.json",
35
+ "README.md"
36
+ ]
37
+ }
@@ -0,0 +1,141 @@
1
+ import type { ChorusMcpClient } from "./mcp-client.js";
2
+
3
+ // ===== Response types from Chorus MCP tools =====
4
+
5
+ interface CheckinResponse {
6
+ checkinTime: string;
7
+ agent: {
8
+ uuid: string;
9
+ name: string;
10
+ roles: string[];
11
+ persona: string | null;
12
+ systemPrompt: string | null;
13
+ };
14
+ assignments: {
15
+ ideas: AssignedIdea[];
16
+ tasks: AssignedTask[];
17
+ };
18
+ pending: {
19
+ ideasCount: number;
20
+ tasksCount: number;
21
+ };
22
+ notifications: {
23
+ unreadCount: number;
24
+ };
25
+ }
26
+
27
+ interface AssignedIdea {
28
+ uuid: string;
29
+ title: string;
30
+ status: string;
31
+ project: { uuid: string; name: string };
32
+ }
33
+
34
+ interface AssignedTask {
35
+ uuid: string;
36
+ title: string;
37
+ status: string;
38
+ priority: string;
39
+ project: { uuid: string; name: string };
40
+ }
41
+
42
+ interface AssignmentsResponse {
43
+ ideas: AssignedIdea[];
44
+ tasks: AssignedTask[];
45
+ }
46
+
47
+ // ===== Formatting helpers =====
48
+
49
+ function formatStatus(checkin: CheckinResponse, connectionStatus: string): string {
50
+ const lines: string[] = [
51
+ `Connection: ${connectionStatus}`,
52
+ `Assignments: ${checkin.pending.ideasCount} ideas, ${checkin.pending.tasksCount} tasks`,
53
+ `Notifications: ${checkin.notifications.unreadCount} unread`,
54
+ ];
55
+ return lines.join("\n");
56
+ }
57
+
58
+ function formatTaskList(tasks: AssignedTask[]): string {
59
+ if (tasks.length === 0) {
60
+ return "No assigned tasks.";
61
+ }
62
+
63
+ const lines = tasks.map(
64
+ (t) => `[${t.status}] [${t.priority}] ${t.title} (${t.project.name})`
65
+ );
66
+ return `Assigned tasks (${tasks.length}):\n${lines.join("\n")}`;
67
+ }
68
+
69
+ function formatIdeaList(ideas: AssignedIdea[]): string {
70
+ if (ideas.length === 0) {
71
+ return "No assigned ideas.";
72
+ }
73
+
74
+ const lines = ideas.map(
75
+ (i) => `[${i.status}] ${i.title} (${i.project.name})`
76
+ );
77
+ return `Assigned ideas (${ideas.length}):\n${lines.join("\n")}`;
78
+ }
79
+
80
+ const HELP_TEXT = [
81
+ "Chorus commands:",
82
+ " /chorus Show connection status and summary",
83
+ " /chorus status Same as above",
84
+ " /chorus tasks List assigned tasks",
85
+ " /chorus ideas List assigned ideas",
86
+ ].join("\n");
87
+
88
+ // ===== Registration =====
89
+
90
+ export function registerChorusCommands(
91
+ api: any,
92
+ mcpClient: ChorusMcpClient,
93
+ getStatus: () => string
94
+ ): void {
95
+ api.registerCommand({
96
+ name: "chorus",
97
+ description: "Chorus plugin commands: status, tasks, ideas",
98
+ async handler(ctx: { args: string }) {
99
+ const sub = (ctx.args ?? "").trim().toLowerCase();
100
+
101
+ // /chorus or /chorus status
102
+ if (!sub || sub === "status") {
103
+ try {
104
+ const checkin = (await mcpClient.callTool("chorus_checkin", {})) as CheckinResponse;
105
+ return { text: formatStatus(checkin, getStatus()) };
106
+ } catch (err) {
107
+ return { text: `Failed to check in: ${err instanceof Error ? err.message : String(err)}` };
108
+ }
109
+ }
110
+
111
+ // /chorus tasks
112
+ if (sub === "tasks") {
113
+ try {
114
+ const data = (await mcpClient.callTool(
115
+ "chorus_get_my_assignments",
116
+ {}
117
+ )) as AssignmentsResponse;
118
+ return { text: formatTaskList(data.tasks) };
119
+ } catch (err) {
120
+ return { text: `Failed to fetch tasks: ${err instanceof Error ? err.message : String(err)}` };
121
+ }
122
+ }
123
+
124
+ // /chorus ideas
125
+ if (sub === "ideas") {
126
+ try {
127
+ const data = (await mcpClient.callTool(
128
+ "chorus_get_my_assignments",
129
+ {}
130
+ )) as AssignmentsResponse;
131
+ return { text: formatIdeaList(data.ideas) };
132
+ } catch (err) {
133
+ return { text: `Failed to fetch ideas: ${err instanceof Error ? err.message : String(err)}` };
134
+ }
135
+ }
136
+
137
+ // Unknown subcommand
138
+ return { text: HELP_TEXT };
139
+ },
140
+ });
141
+ }
package/src/config.ts ADDED
@@ -0,0 +1,24 @@
1
+ import { z } from "zod";
2
+
3
+ export const chorusConfigSchema = z.object({
4
+ chorusUrl: z
5
+ .string()
6
+ .url()
7
+ .describe("Chorus server URL (e.g. https://chorus.example.com)"),
8
+ apiKey: z
9
+ .string()
10
+ .startsWith("cho_")
11
+ .describe("Chorus API Key (cho_ prefix)"),
12
+ projectUuids: z
13
+ .array(z.string().uuid())
14
+ .optional()
15
+ .default([])
16
+ .describe("Project UUIDs to monitor. Empty = all projects"),
17
+ autoStart: z
18
+ .boolean()
19
+ .optional()
20
+ .default(true)
21
+ .describe("Auto-claim and start work on task_assigned events"),
22
+ });
23
+
24
+ export type ChorusPluginConfig = z.infer<typeof chorusConfigSchema>;