@chorus-aidlc/chorus-openclaw-plugin 0.4.0 → 0.5.3
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 +208 -278
- package/dist/commands.d.ts +5 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +147 -0
- package/dist/commands.js.map +1 -0
- package/dist/config.d.ts +38 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +57 -0
- package/dist/config.js.map +1 -0
- package/dist/event-router.d.ts +55 -0
- package/dist/event-router.d.ts.map +1 -0
- package/dist/event-router.js +157 -0
- package/dist/event-router.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +108 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-client.d.ts +37 -0
- package/dist/mcp-client.d.ts.map +1 -0
- package/dist/mcp-client.js +137 -0
- package/dist/mcp-client.js.map +1 -0
- package/dist/mcp-registration.d.ts +25 -0
- package/dist/mcp-registration.d.ts.map +1 -0
- package/dist/mcp-registration.js +93 -0
- package/dist/mcp-registration.js.map +1 -0
- package/dist/sse-listener.d.ts +37 -0
- package/dist/sse-listener.d.ts.map +1 -0
- package/dist/sse-listener.js +152 -0
- package/dist/sse-listener.js.map +1 -0
- package/dist/wake.d.ts +67 -0
- package/dist/wake.d.ts.map +1 -0
- package/dist/wake.js +234 -0
- package/dist/wake.js.map +1 -0
- package/openclaw.plugin.json +13 -12
- package/package.json +23 -5
- package/skills/brainstorm/SKILL.md +163 -0
- package/skills/chorus/SKILL.md +114 -97
- package/skills/develop/SKILL.md +197 -52
- package/skills/idea/SKILL.md +136 -150
- package/skills/openspec-aware/SKILL.md +425 -0
- package/skills/proposal/SKILL.md +162 -153
- package/skills/proposal-reviewer/SKILL.md +118 -0
- package/skills/quick-dev/SKILL.md +34 -10
- package/skills/review/SKILL.md +109 -35
- package/skills/task-reviewer/SKILL.md +113 -0
- package/skills/yolo/SKILL.md +501 -0
- package/src/commands.ts +138 -71
- package/src/config.ts +23 -10
- package/src/event-router.ts +46 -54
- package/src/index.ts +56 -83
- package/src/mcp-client.ts +17 -0
- package/src/mcp-registration.ts +142 -0
- package/src/openclaw-sdk.d.ts +95 -0
- package/src/wake.ts +310 -0
- package/src/tools/admin-tools.ts +0 -126
- package/src/tools/common-tools.ts +0 -575
- package/src/tools/dev-tools.ts +0 -105
- package/src/tools/pm-tools.ts +0 -411
package/dist/wake.js
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wake mechanism for the Chorus plugin.
|
|
3
|
+
*
|
|
4
|
+
* Runs an agent turn IN-PROCESS with the wake text as the agent's prompt, via
|
|
5
|
+
* `api.runtime.agent.runEmbeddedAgent(...)` — the same primitive the cron
|
|
6
|
+
* service uses to "run the agent now with this prompt"
|
|
7
|
+
* (`../openclaw/src/cron/isolated-agent/run-executor.ts:266`).
|
|
8
|
+
*
|
|
9
|
+
* WHY NOT enqueueSystemEvent + heartbeat: that path was tried and does NOT
|
|
10
|
+
* deliver our text. `enqueueSystemEvent` only pushes onto the session queue,
|
|
11
|
+
* and the heartbeat prompt builder only renders exec-completion / cron events
|
|
12
|
+
* into a prompt (`../openclaw/src/infra/heartbeat-runner.ts:1240-1252`); a plain
|
|
13
|
+
* notification event is never injected, so the agent turned on the generic
|
|
14
|
+
* `[OpenClaw heartbeat poll]` prompt with no Chorus content. runEmbeddedAgent
|
|
15
|
+
* delivers the prompt directly and runs a real turn that can call the MCP tools.
|
|
16
|
+
*
|
|
17
|
+
* The runtime surface used here is declared on `PluginRuntimeCore.agent` in
|
|
18
|
+
* `../openclaw/src/plugins/runtime/types-core.ts:180-221`. The local SDK shim
|
|
19
|
+
* types `api.runtime` permissively, so we narrow the slice we use inline —
|
|
20
|
+
* mirroring `mcp-registration.ts` (api.runtime.config). Verified signatures:
|
|
21
|
+
* - runEmbeddedAgent(RunEmbeddedAgentParams) — required: sessionId,
|
|
22
|
+
* sessionFile, workspaceDir, prompt, timeoutMs, runId (params.ts:39+).
|
|
23
|
+
* - agent.session.getSessionEntry({ sessionKey, agentId }) → SessionEntry?
|
|
24
|
+
* (store.ts:210); SessionEntry has sessionId + optional sessionFile.
|
|
25
|
+
* - agent.session.resolveSessionFilePath(sessionId, { sessionFile }, { agentId })
|
|
26
|
+
* (paths.ts:267).
|
|
27
|
+
* - agent.resolveAgentWorkspaceDir(cfg, agentId) / resolveAgentDir(cfg, agentId)
|
|
28
|
+
* — positional (agent-scope-config.ts:170/195).
|
|
29
|
+
* - agent.resolveAgentTimeoutMs({ cfg }) → number (timeout.ts:15).
|
|
30
|
+
*/
|
|
31
|
+
const FALLBACK_DEFAULT_AGENT_ID = "main";
|
|
32
|
+
const DEFAULT_MAIN_KEY = "main";
|
|
33
|
+
/** Mirror of OpenClaw's `normalizeAgentId` lowercasing fallback. */
|
|
34
|
+
function normalizeAgentId(value) {
|
|
35
|
+
const trimmed = (value ?? "").trim().toLowerCase();
|
|
36
|
+
if (!trimmed)
|
|
37
|
+
return FALLBACK_DEFAULT_AGENT_ID;
|
|
38
|
+
// Keep it path-safe + shell-friendly (matches openclaw VALID_ID_RE behavior).
|
|
39
|
+
if (/^[a-z0-9][a-z0-9_-]{0,63}$/.test(trimmed))
|
|
40
|
+
return trimmed;
|
|
41
|
+
const collapsed = trimmed
|
|
42
|
+
.replace(/[^a-z0-9_-]+/g, "-")
|
|
43
|
+
.replace(/^-+/, "")
|
|
44
|
+
.replace(/-+$/, "")
|
|
45
|
+
.slice(0, 64);
|
|
46
|
+
return collapsed || FALLBACK_DEFAULT_AGENT_ID;
|
|
47
|
+
}
|
|
48
|
+
/** Mirror of OpenClaw's `normalizeMainKey`. */
|
|
49
|
+
function normalizeMainKey(value) {
|
|
50
|
+
const trimmed = (value ?? "").trim().toLowerCase();
|
|
51
|
+
return trimmed || DEFAULT_MAIN_KEY;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Resolve the main agent session key from the host config.
|
|
55
|
+
*
|
|
56
|
+
* This faithfully replicates OpenClaw's `resolveMainSessionKey(cfg)`
|
|
57
|
+
* (`../openclaw/src/config/sessions/main-session.ts:14`): a background plugin
|
|
58
|
+
* has no inbound message route to derive a session key from, so the wake must
|
|
59
|
+
* target the main agent's session. We cannot import the OpenClaw internal here
|
|
60
|
+
* (the resolvable `openclaw` peer is an older build), so we re-derive the key
|
|
61
|
+
* from `api.config` (the live `OpenClawConfig` snapshot) using the same rules:
|
|
62
|
+
*
|
|
63
|
+
* - `session.scope === "global"` → the literal `"global"` queue
|
|
64
|
+
* - otherwise → `agent:<defaultAgentId>:<mainKey>`, where defaultAgentId is
|
|
65
|
+
* the agent flagged `default`, else the first listed agent, else "main".
|
|
66
|
+
*
|
|
67
|
+
* Returns `null` when no session key can be resolved. The design's no-session
|
|
68
|
+
* fallback (graceful drop) then applies — we never fabricate a session.
|
|
69
|
+
*/
|
|
70
|
+
export function resolveSessionKey(api) {
|
|
71
|
+
const cfg = api.config;
|
|
72
|
+
if (!cfg)
|
|
73
|
+
return null;
|
|
74
|
+
if (cfg.session?.scope === "global") {
|
|
75
|
+
return "global";
|
|
76
|
+
}
|
|
77
|
+
const agents = Array.isArray(cfg.agents?.list) ? cfg.agents.list : [];
|
|
78
|
+
const defaultAgentId = agents.find((agent) => agent?.default)?.id ?? agents[0]?.id ?? FALLBACK_DEFAULT_AGENT_ID;
|
|
79
|
+
const key = `agent:${normalizeAgentId(defaultAgentId)}:${normalizeMainKey(cfg.session?.mainKey)}`;
|
|
80
|
+
return key || null;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Resolve the default agent id (matches OpenClaw's `resolveDefaultAgentId`):
|
|
84
|
+
* the agent flagged `default`, else the first listed agent, else "main".
|
|
85
|
+
* runEmbeddedAgent and the session/workspace resolvers are all agent-scoped.
|
|
86
|
+
*/
|
|
87
|
+
export function resolveAgentId(api) {
|
|
88
|
+
const cfg = api.config;
|
|
89
|
+
const agents = Array.isArray(cfg?.agents?.list) ? cfg.agents.list : [];
|
|
90
|
+
const id = agents.find((a) => a?.default)?.id ?? agents[0]?.id ?? FALLBACK_DEFAULT_AGENT_ID;
|
|
91
|
+
return normalizeAgentId(id);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Resolve the configured default model into `{ provider, model }` so we can pass
|
|
95
|
+
* them explicitly to runEmbeddedAgent. WHY THIS IS REQUIRED: runEmbeddedAgent
|
|
96
|
+
* does NOT auto-resolve the model from config when `provider`/`model` are
|
|
97
|
+
* omitted — it falls back to the built-in `DEFAULT_MODEL` ("gpt-5.5",
|
|
98
|
+
* `../openclaw/src/agents/defaults.ts:4`), which fails with "Unknown model"
|
|
99
|
+
* unless that happens to be configured. The host's own resolver
|
|
100
|
+
* (`resolveConfiguredModelRef`, `../openclaw/src/agents/model-selection-shared.ts`)
|
|
101
|
+
* is NOT exposed to plugins, so we mirror its primary-ref read: `agents.defaults.model`
|
|
102
|
+
* is either a bare `"provider/model"` string or `{ primary: "provider/model" }`
|
|
103
|
+
* (`resolvePrimaryStringValue`), split on the first "/".
|
|
104
|
+
*
|
|
105
|
+
* Returns null when no default model is configured (then we omit the overrides
|
|
106
|
+
* and let the host fall back — same as before, but at least we tried).
|
|
107
|
+
*/
|
|
108
|
+
export function resolveModelRef(api) {
|
|
109
|
+
const model = api.config
|
|
110
|
+
?.agents?.defaults?.model;
|
|
111
|
+
const raw = typeof model === "string"
|
|
112
|
+
? model
|
|
113
|
+
: model && typeof model === "object"
|
|
114
|
+
? model.primary
|
|
115
|
+
: undefined;
|
|
116
|
+
const ref = typeof raw === "string" ? raw.trim() : "";
|
|
117
|
+
const slash = ref.indexOf("/");
|
|
118
|
+
if (slash <= 0 || slash >= ref.length - 1) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
return { provider: ref.slice(0, slash), model: ref.slice(slash + 1) };
|
|
122
|
+
}
|
|
123
|
+
function getRuntimeAgent(api) {
|
|
124
|
+
const agent = api.runtime?.agent;
|
|
125
|
+
if (!agent ||
|
|
126
|
+
typeof agent.runEmbeddedAgent !== "function" ||
|
|
127
|
+
typeof agent.resolveAgentWorkspaceDir !== "function" ||
|
|
128
|
+
typeof agent.resolveAgentTimeoutMs !== "function" ||
|
|
129
|
+
typeof agent.session?.getSessionEntry !== "function" ||
|
|
130
|
+
typeof agent.session?.resolveSessionFilePath !== "function") {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
return agent;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Build a stable-ish unique id without Date.now()/Math.random() (some hosts
|
|
137
|
+
* sandbox those). A monotonic counter + the contextKey is enough for runId
|
|
138
|
+
* uniqueness within a process.
|
|
139
|
+
*/
|
|
140
|
+
let wakeCounter = 0;
|
|
141
|
+
function nextRunId(contextKey) {
|
|
142
|
+
wakeCounter += 1;
|
|
143
|
+
return `chorus-wake-${wakeCounter}-${contextKey}`;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Build a `wake(message, contextKey)` callback bound to the host runtime.
|
|
147
|
+
*
|
|
148
|
+
* Each wake resolves the main agent's session + workspace and runs an agent
|
|
149
|
+
* turn IN-PROCESS with `message` as the prompt (via runEmbeddedAgent). The turn
|
|
150
|
+
* has the agent's normal tool set (including the `chorus__*` MCP tools), so the
|
|
151
|
+
* agent can read the entity, the comment thread, and post back to Chorus.
|
|
152
|
+
*
|
|
153
|
+
* Failure modes (never throw — the SSE service must stay alive):
|
|
154
|
+
* - no resolvable session key / agent runtime unavailable → log + drop;
|
|
155
|
+
* - no existing session entry → run with a fresh sessionId (the runner
|
|
156
|
+
* creates the transcript); the agent starts a new conversation;
|
|
157
|
+
* - runEmbeddedAgent rejects (e.g. a turn is already in flight) → log + drop
|
|
158
|
+
* (the next SSE event re-triggers).
|
|
159
|
+
*
|
|
160
|
+
* Runs are fire-and-forget (the SSE dispatch path is sync); we log completion.
|
|
161
|
+
*/
|
|
162
|
+
export function createWake(api, logger) {
|
|
163
|
+
return (message, contextKey) => {
|
|
164
|
+
const sessionKey = resolveSessionKey(api);
|
|
165
|
+
if (!sessionKey) {
|
|
166
|
+
logger.warn(`[Chorus] Wake DROPPED — could not resolve a main agent session key (contextKey=${contextKey}). Event: ${message.slice(0, 100)}`);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const agent = getRuntimeAgent(api);
|
|
170
|
+
if (!agent) {
|
|
171
|
+
logger.warn(`[Chorus] Wake DROPPED — api.runtime.agent.runEmbeddedAgent (or a required session helper) is unavailable on this host (contextKey=${contextKey}).`);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const cfg = api.config;
|
|
175
|
+
const agentId = resolveAgentId(api);
|
|
176
|
+
// Resolve the EXISTING main-agent session so the wake turn continues the
|
|
177
|
+
// real conversation (context + MCP tools). If none exists yet, fall back to
|
|
178
|
+
// a fresh session id — runEmbeddedAgent creates the transcript file.
|
|
179
|
+
let sessionId;
|
|
180
|
+
let sessionFile;
|
|
181
|
+
try {
|
|
182
|
+
const entry = agent.session.getSessionEntry({ sessionKey, agentId });
|
|
183
|
+
sessionId = entry?.sessionId ?? nextRunId(contextKey);
|
|
184
|
+
sessionFile = agent.session.resolveSessionFilePath(sessionId, entry?.sessionFile ? { sessionFile: entry.sessionFile } : undefined, { agentId });
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
logger.warn(`[Chorus] Wake DROPPED — session resolution failed (contextKey=${contextKey}): ${err}`);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
let workspaceDir;
|
|
191
|
+
let agentDir;
|
|
192
|
+
let timeoutMs;
|
|
193
|
+
try {
|
|
194
|
+
workspaceDir = agent.resolveAgentWorkspaceDir(cfg, agentId);
|
|
195
|
+
agentDir = agent.resolveAgentDir(cfg, agentId);
|
|
196
|
+
timeoutMs = agent.resolveAgentTimeoutMs({ cfg });
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
logger.warn(`[Chorus] Wake DROPPED — workspace/timeout resolution failed (contextKey=${contextKey}): ${err}`);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
// Resolve the configured model; runEmbeddedAgent otherwise falls back to the
|
|
203
|
+
// built-in DEFAULT_MODEL ("gpt-5.5") and fails with "Unknown model".
|
|
204
|
+
const modelRef = resolveModelRef(api);
|
|
205
|
+
if (!modelRef) {
|
|
206
|
+
logger.warn(`[Chorus] No agents.defaults.model configured — wake turn will use the host default model, which may be unavailable (contextKey=${contextKey}).`);
|
|
207
|
+
}
|
|
208
|
+
const runId = nextRunId(contextKey);
|
|
209
|
+
logger.info(`[Chorus] Waking agent via embedded run (sessionKey=${sessionKey}, model=${modelRef ? `${modelRef.provider}/${modelRef.model}` : "host-default"}, contextKey=${contextKey})`);
|
|
210
|
+
// Fire-and-forget: the SSE dispatch path is synchronous, and a wake turn
|
|
211
|
+
// can take many seconds. We log completion/failure but never await here.
|
|
212
|
+
// `disableMessageTool: true` → headless turn; the agent acts via MCP tools
|
|
213
|
+
// (chorus_add_comment, etc.) rather than replying to a chat channel.
|
|
214
|
+
void Promise.resolve()
|
|
215
|
+
.then(() => agent.runEmbeddedAgent({
|
|
216
|
+
sessionId,
|
|
217
|
+
sessionKey,
|
|
218
|
+
agentId,
|
|
219
|
+
trigger: "manual",
|
|
220
|
+
sessionFile,
|
|
221
|
+
...(agentDir ? { agentDir } : {}),
|
|
222
|
+
workspaceDir,
|
|
223
|
+
config: cfg,
|
|
224
|
+
prompt: message,
|
|
225
|
+
timeoutMs,
|
|
226
|
+
runId,
|
|
227
|
+
disableMessageTool: true,
|
|
228
|
+
...(modelRef ? { provider: modelRef.provider, model: modelRef.model } : {}),
|
|
229
|
+
}))
|
|
230
|
+
.then(() => logger.info(`[Chorus] Wake turn completed (sessionKey=${sessionKey}, contextKey=${contextKey})`))
|
|
231
|
+
.catch((err) => logger.warn(`[Chorus] Wake turn failed (sessionKey=${sessionKey}, contextKey=${contextKey}): ${err instanceof Error ? err.message : String(err)}`));
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
//# sourceMappingURL=wake.js.map
|
package/dist/wake.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wake.js","sourceRoot":"","sources":["../src/wake.ts"],"names":[],"mappings":"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"}
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "chorus-openclaw-plugin",
|
|
3
|
+
"name": "Chorus",
|
|
4
|
+
"description": "Chorus AI-DLC collaboration platform — native MCP + SSE real-time events",
|
|
3
5
|
"skills": ["./skills"],
|
|
6
|
+
"activation": {
|
|
7
|
+
"onStartup": true
|
|
8
|
+
},
|
|
9
|
+
"uiHints": {
|
|
10
|
+
"apiKey": {
|
|
11
|
+
"label": "Chorus API Key",
|
|
12
|
+
"placeholder": "cho_...",
|
|
13
|
+
"sensitive": true
|
|
14
|
+
}
|
|
15
|
+
},
|
|
4
16
|
"configSchema": {
|
|
5
17
|
"type": "object",
|
|
6
18
|
"additionalProperties": false,
|
|
7
19
|
"properties": {
|
|
8
20
|
"chorusUrl": {
|
|
9
21
|
"type": "string",
|
|
10
|
-
"description": "Chorus server URL"
|
|
22
|
+
"description": "Chorus server URL (e.g. https://chorus.example.com)"
|
|
11
23
|
},
|
|
12
24
|
"apiKey": {
|
|
13
25
|
"type": "string",
|
|
14
26
|
"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
|
}
|
|
27
28
|
}
|
|
28
29
|
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chorus-aidlc/chorus-openclaw-plugin",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "OpenClaw plugin for Chorus AI-DLC collaboration platform — SSE real-time events
|
|
3
|
+
"version": "0.5.3",
|
|
4
|
+
"description": "OpenClaw plugin for Chorus AI-DLC collaboration platform — native MCP + SSE real-time events",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "src/index.ts",
|
|
8
8
|
"scripts": {
|
|
9
|
-
"
|
|
9
|
+
"build": "tsc",
|
|
10
|
+
"typecheck": "tsc --noEmit",
|
|
11
|
+
"test": "vitest run",
|
|
12
|
+
"test:watch": "vitest",
|
|
13
|
+
"clean": "rm -rf dist",
|
|
14
|
+
"release": "bash bin/publish.sh",
|
|
15
|
+
"prepublishOnly": "npm run clean && npm run typecheck && npm run test && npm run build"
|
|
10
16
|
},
|
|
11
17
|
"peerDependencies": {
|
|
12
18
|
"openclaw": ">=2026.0.0"
|
|
@@ -16,10 +22,20 @@
|
|
|
16
22
|
"zod": "^3.23.0"
|
|
17
23
|
},
|
|
18
24
|
"devDependencies": {
|
|
19
|
-
"typescript": "^5.9.0"
|
|
25
|
+
"typescript": "^5.9.0",
|
|
26
|
+
"vitest": "^3.2.0"
|
|
20
27
|
},
|
|
21
28
|
"openclaw": {
|
|
22
|
-
"extensions": ["src/index.ts"]
|
|
29
|
+
"extensions": ["./src/index.ts"],
|
|
30
|
+
"runtimeExtensions": ["./dist/index.js"],
|
|
31
|
+
"install": {
|
|
32
|
+
"npmSpec": "@chorus-aidlc/chorus-openclaw-plugin",
|
|
33
|
+
"defaultChoice": "npm",
|
|
34
|
+
"minHostVersion": ">=2026.4.27"
|
|
35
|
+
},
|
|
36
|
+
"compat": {
|
|
37
|
+
"pluginApi": ">=2026.4.27"
|
|
38
|
+
}
|
|
23
39
|
},
|
|
24
40
|
"repository": {
|
|
25
41
|
"type": "git",
|
|
@@ -30,6 +46,8 @@
|
|
|
30
46
|
"keywords": ["openclaw", "chorus", "ai-dlc", "mcp", "plugin", "agent"],
|
|
31
47
|
"files": [
|
|
32
48
|
"src",
|
|
49
|
+
"!src/**/__tests__",
|
|
50
|
+
"dist",
|
|
33
51
|
"skills",
|
|
34
52
|
"openclaw.plugin.json",
|
|
35
53
|
"README.md"
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: brainstorm
|
|
3
|
+
description: Optional divergent-then-convergent dialogue for fuzzy ideas. Invoked from the idea skill as a prelude to structured elaboration; produces one ElaborationRound of decision-point Q&A and returns control. Never writes files, never posts comments, never validates elaboration.
|
|
4
|
+
license: AGPL-3.0
|
|
5
|
+
metadata:
|
|
6
|
+
author: chorus
|
|
7
|
+
version: "0.9.4"
|
|
8
|
+
category: project-management
|
|
9
|
+
mcp_server: chorus
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Brainstorm Skill
|
|
13
|
+
|
|
14
|
+
A divergent-then-convergent dialogue cadence for ideas whose direction is still being formed. Compresses the conversation into one `ElaborationRound` of decision-point Q&A — same shape as a structured elaboration round, but the questions, options and answers are synthesized at the end of the conversation rather than asked up front.
|
|
15
|
+
|
|
16
|
+
This skill is a **producer** of one elaboration round; the **scheduler** decision (validate vs. follow-up) belongs to the calling idea skill.
|
|
17
|
+
|
|
18
|
+
> **Tool namespace:** Chorus tools are exposed by the connected MCP server under a `chorus__` prefix on OpenClaw (e.g. `chorus__chorus_get_idea`). Bare names are used below for readability — prepend `chorus__` when invoking. See `/chorus` for the full rule.
|
|
19
|
+
|
|
20
|
+
> **OpenClaw interaction model:** OpenClaw has no `AskUserQuestion` primitive. Every "ask the user" step below is a **plain-text prompt** — write the question (and options) in your message and wait for the user's free-text reply before continuing. The one-question-at-a-time cadence still holds: send one question, read the answer, then send the next.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## When invoked
|
|
25
|
+
|
|
26
|
+
Only as a sub-step of the idea skill, only after the user has explicitly opted in (via the idea skill's plain-text Brainstorm prompt). Never run standalone, never run without user opt-in. The expected entry point is the idea skill's "Step 4.5: Brainstorm Mode (Optional Prelude)" — see the idea skill for the surrounding flow.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Hard rules
|
|
31
|
+
|
|
32
|
+
1. **One question at a time.** Each prompt MUST contain exactly one question. Wait for the user's reply before asking the next.
|
|
33
|
+
2. **Multi-choice preferred.** Frame each question as 2-4 options where possible (offer them as a lettered list in your plain-text prompt). Open-ended is acceptable when options would be premature, but lean toward concrete choices.
|
|
34
|
+
3. **Propose 2-3 directions before stopping divergence.** Once the requirement direction is clear enough to enumerate, present 2-3 distinct approaches in a single prompt. Mark exactly one as the recommended option and say why (the dominant tradeoff).
|
|
35
|
+
4. **Explicit user approval required to exit divergence.** Do NOT proceed to synthesis until the user has selected one of the proposed directions in their reply.
|
|
36
|
+
5. **No files written.** Do NOT write any markdown, design doc, scratch file, or any other file to disk. The conversation produces an `ElaborationRound` and nothing else on disk.
|
|
37
|
+
6. **No comments posted.** Do NOT call `chorus_add_comment` from this skill. Comments belong to the idea skill or the user, not to the brainstorm step.
|
|
38
|
+
7. **No design-doc handoff.** Do NOT invoke `writing-plans`, `writing-skills`, or any skill whose purpose is to produce a design document. The brainstorm output is the synthesized round — there is no separate doc.
|
|
39
|
+
8. **No `validate_elaboration` call.** Do NOT call `chorus_pm_validate_elaboration` from this skill. Whether to resolve the elaboration or open a follow-up round (`chorus_pm_start_elaboration` again) is the calling idea skill's decision, not this skill's.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Step-by-step
|
|
44
|
+
|
|
45
|
+
### 1. Gather context
|
|
46
|
+
|
|
47
|
+
Before asking the first divergent question, read the idea and surrounding project state. Mirror the idea skill's gather-context list:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
chorus_get_idea({ ideaUuid })
|
|
51
|
+
chorus_get_documents({ projectUuid })
|
|
52
|
+
chorus_get_document({ documentUuid }) # for any document worth reading in full
|
|
53
|
+
chorus_get_proposals({ projectUuid, status: "approved" }) # to understand patterns
|
|
54
|
+
chorus_list_tasks({ projectUuid }) # to avoid duplicating existing work
|
|
55
|
+
chorus_get_comments({ targetType: "idea", targetUuid: ideaUuid })
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Skim each result for: stated background, stated requirements, stated constraints, and what is conspicuously NOT stated. The gaps are the questions worth asking.
|
|
59
|
+
|
|
60
|
+
### 2. Divergent Q&A
|
|
61
|
+
|
|
62
|
+
Ask one question at a time as a plain-text prompt. Aim to surface:
|
|
63
|
+
|
|
64
|
+
- The **goal** the idea is trying to serve (often more abstract than the idea statement).
|
|
65
|
+
- The **constraints** that exclude entire branches of solution space (deadlines, compatibility, scope).
|
|
66
|
+
- The **success criteria** — how will the user know this is done.
|
|
67
|
+
|
|
68
|
+
Keep each question single-purpose. If you need to ask three things, that is three prompts, not one combined message.
|
|
69
|
+
|
|
70
|
+
### 3. Propose 2-3 directions
|
|
71
|
+
|
|
72
|
+
When the goal, constraints, and success criteria are clear enough that you can name distinct approaches, present them in a single plain-text prompt:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
Based on what you've told me, here are three directions. Which do you want? (reply A / B / C, or describe your own)
|
|
76
|
+
|
|
77
|
+
A) <Option A — RECOMMENDED> — <what + tradeoff>
|
|
78
|
+
B) <Option B> — <what + tradeoff>
|
|
79
|
+
C) <Option C> — <what + tradeoff>
|
|
80
|
+
|
|
81
|
+
I recommend A because <one sentence about the dominant tradeoff>.
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
State **why** you recommend one option — usually a sentence about the dominant tradeoff.
|
|
85
|
+
|
|
86
|
+
### 4. Wait for explicit approval
|
|
87
|
+
|
|
88
|
+
Do not proceed to synthesis if the user has not selected one of the options in their reply. If the user picks "Other" with free text, treat that as a new constraint — go back to step 2 or step 3 with the refined direction.
|
|
89
|
+
|
|
90
|
+
### 5. Synthesize decision-point Q&A
|
|
91
|
+
|
|
92
|
+
For each material decision the user made during the conversation, build one `ElaborationQuestion`. A "material decision" is a moment where the user chose between alternatives or set scope explicitly. Map each decision per the synthesis spec below.
|
|
93
|
+
|
|
94
|
+
### 6. Persist the round
|
|
95
|
+
|
|
96
|
+
Call `chorus_pm_start_elaboration` with the synthesized questions:
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
chorus_pm_start_elaboration({
|
|
100
|
+
ideaUuid,
|
|
101
|
+
depth: "standard",
|
|
102
|
+
questions: [
|
|
103
|
+
{ id: "q1", text: "...", category: "...", options: [...] },
|
|
104
|
+
...
|
|
105
|
+
]
|
|
106
|
+
})
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Then submit the answers in one call:
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
chorus_answer_elaboration({
|
|
113
|
+
ideaUuid,
|
|
114
|
+
roundUuid,
|
|
115
|
+
answers: [
|
|
116
|
+
{ questionId: "q1", selectedOptionId: "...", customText: "<rationale>" },
|
|
117
|
+
...
|
|
118
|
+
]
|
|
119
|
+
})
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### 7. Return control
|
|
123
|
+
|
|
124
|
+
Stop here. Do **NOT** call `chorus_pm_validate_elaboration`. The idea skill's caller now decides:
|
|
125
|
+
|
|
126
|
+
- If the synthesized round answers cover everything → caller obtains human confirmation, then resolves with `chorus_pm_validate_elaboration`.
|
|
127
|
+
- If gaps remain → caller opens a structured Round 2 with `chorus_pm_start_elaboration`.
|
|
128
|
+
|
|
129
|
+
The depth of any follow-up round is the caller's call, not yours.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Synthesis spec
|
|
134
|
+
|
|
135
|
+
Each material decision becomes exactly one `ElaborationQuestion` with these fields:
|
|
136
|
+
|
|
137
|
+
| Field | Source |
|
|
138
|
+
|---|---|
|
|
139
|
+
| `text` | The decision question, phrased neutrally. Example: "Which depth-model placement?" |
|
|
140
|
+
| `category` | `functional`, `non_functional`, `business_context`, `technical_context`, `user_scenario`, or `scope` — derived from the topic. |
|
|
141
|
+
| `options` | All directions that were considered, length 2-5. Collapse near-duplicates into one option. |
|
|
142
|
+
| `selectedOptionId` | The id of the option the user approved. |
|
|
143
|
+
| `customText` | A 1-3 sentence rationale capturing the constraint or tradeoff that drove the choice. Not a transcript dump. |
|
|
144
|
+
|
|
145
|
+
Rules:
|
|
146
|
+
|
|
147
|
+
- A `customText` longer than ~3 sentences is a sign you are summarizing transcript instead of capturing rationale. Cut.
|
|
148
|
+
- An `options` array of length 2 with binary "yes / no" framing is a sign you pre-narrowed alternatives. Re-examine — there are usually at least three meaningfully different paths, even if two of them get rejected quickly.
|
|
149
|
+
- Skip "decisions" that were never genuinely contested. If the user agreed instantly to the only proposal, that is information for the idea content, not a decision-point Q&A.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Anti-patterns
|
|
154
|
+
|
|
155
|
+
Do not do any of the following. Each has a specific failure mode that this skill must prevent:
|
|
156
|
+
|
|
157
|
+
- **Single-summary `customText` blob.** Compressing the entire conversation into one ElaborationQuestion with a long markdown summary in `customText`. The schema is multi-question for a reason — preserve the decision granularity.
|
|
158
|
+
- **Transcript-as-comment.** Posting the raw conversation log as a comment on the idea (or anywhere). The synthesized round IS the artifact. Raw transcripts pollute the audit trail with noise.
|
|
159
|
+
- **File writes.** Writing any markdown, design doc, plan, or scratch file to disk. There is no design doc in this flow. This is a deliberate divergence from the upstream `superpowers/brainstorming` cadence.
|
|
160
|
+
- **`validate_elaboration` calls.** Closing the elaboration phase from this skill. The lifecycle decision belongs to the idea skill. Calling it here strips the caller of its scheduler role.
|
|
161
|
+
- **`writing-plans` / design-doc handoff.** Invoking any skill that produces an implementation plan or design document. The Chorus pipeline already has Proposal → Document Drafts → Task Drafts for that — the brainstorm output feeds them through ElaborationRound, not through external doc skills.
|
|
162
|
+
- **Length-2 binary "yes / no" framings.** Reducing every decision to "do this thing — yes / no". Almost always the genuine alternatives are 3+ approaches with meaningfully different tradeoffs. Length-2 framings often mean the divergent phase ended too early.
|
|
163
|
+
- **Asking multiple questions in one prompt.** The cadence is one question per turn during divergence, then one final convergence prompt with 2-3 options. Combining unrelated questions is a sign you are rushing.
|