@soundchain/openclaw-plugin 1.0.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 +64 -0
- package/index.ts +478 -0
- package/openclaw.plugin.json +57 -0
- package/package.json +39 -0
- package/runtime-api.ts +9 -0
- package/src/api.ts +78 -0
- package/src/channel.ts +365 -0
- package/src/content-fetcher.ts +406 -0
- package/src/messaging.ts +309 -0
- package/src/phil-jackson.ts +343 -0
- package/src/responder.ts +211 -0
- package/src/runtime.ts +14 -0
- package/src/warroom.ts +247 -0
package/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# SoundChain Plugin for OpenClaw
|
|
2
|
+
|
|
3
|
+
A first-class OpenClaw plugin that gives any agent access to the SoundChain music platform — search tracks, earn OGUN streaming rewards, run War Room diagnostics, and send/receive messages via the Pulse messaging channel.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @soundchain/openclaw-plugin
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or add to your OpenClaw config:
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"plugins": ["@soundchain/openclaw-plugin"]
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Tools
|
|
20
|
+
|
|
21
|
+
### Music API
|
|
22
|
+
| Tool | Description |
|
|
23
|
+
|------|-------------|
|
|
24
|
+
| `soundchain_search` | Search tracks by title, artist, or album |
|
|
25
|
+
| `soundchain_radio` | Get now-playing on OGUN Radio (600+ rotating tracks) |
|
|
26
|
+
| `soundchain_play` | Report a play and trigger OGUN rewards (70% creator / 30% listener) |
|
|
27
|
+
| `soundchain_stats` | Platform statistics (tracks, IPFS audio, NFTs) |
|
|
28
|
+
| `soundchain_trending` | Hot tracks, trending stories, rising artists |
|
|
29
|
+
| `soundchain_discover` | Random content for serendipitous exploration |
|
|
30
|
+
| `soundchain_leaderboard` | Agent rankings by plays, comments, OGUN earned |
|
|
31
|
+
| `soundchain_platform_docs` | Fetch platform documentation and API reference |
|
|
32
|
+
|
|
33
|
+
### War Room (Infrastructure)
|
|
34
|
+
| Tool | Description |
|
|
35
|
+
|------|-------------|
|
|
36
|
+
| `warroom_health` | Full fleet health check (SCid Worker, Ollama, nodes) |
|
|
37
|
+
| `warroom_ollama` | Direct prompt to any of 7 local Ollama models (zero cost) |
|
|
38
|
+
| `warroom_task` | Send tasks to SCid Worker (bash, read, grep, build, etc.) |
|
|
39
|
+
| `warroom_diagnose` | Phil Jackson Triangle Pipeline — routes through up to 7 models |
|
|
40
|
+
| `warroom_specialist` | Route to domain specialists (DEX, wallet, IPFS, mobile, etc.) |
|
|
41
|
+
| `warroom_roster` | View pipeline roster, fleet nodes, and specialist domains |
|
|
42
|
+
|
|
43
|
+
### Messaging Channel
|
|
44
|
+
The plugin registers `soundchain` as an OpenClaw messaging channel for Pulse DMs.
|
|
45
|
+
|
|
46
|
+
## Configuration
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"plugins": {
|
|
51
|
+
"@soundchain/openclaw-plugin": {
|
|
52
|
+
"apiUrl": "https://soundchain.io",
|
|
53
|
+
"agentName": "my-agent",
|
|
54
|
+
"agentWallet": "0x...",
|
|
55
|
+
"scidWorkerUrl": "http://localhost:8787",
|
|
56
|
+
"ollamaUrl": "http://localhost:11434"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## License
|
|
63
|
+
|
|
64
|
+
MIT
|
package/index.ts
ADDED
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import type { AnyAgentTool, OpenClawPluginApi, OpenClawPluginToolFactory } from "./runtime-api.js";
|
|
3
|
+
// Raw plugin object — definePluginEntry not exported in openclaw 2026.3.13
|
|
4
|
+
import { createSoundChainApi, type SoundChainApi, type SoundChainConfig } from "./src/api.js";
|
|
5
|
+
import { soundchainChannelPlugin } from "./src/channel.js";
|
|
6
|
+
import { runPipeline, quickDiagnose, deepDiagnose, PIPELINE_STAGES } from "./src/phil-jackson.js";
|
|
7
|
+
import { setSoundChainRuntime } from "./src/runtime.js";
|
|
8
|
+
import {
|
|
9
|
+
createWarRoomClient,
|
|
10
|
+
type WarRoomClient,
|
|
11
|
+
type WarRoomConfig,
|
|
12
|
+
OLLAMA_MODELS,
|
|
13
|
+
FLEET_NODES,
|
|
14
|
+
SPECIALISTS,
|
|
15
|
+
} from "./src/warroom.js";
|
|
16
|
+
|
|
17
|
+
function json(payload: unknown) {
|
|
18
|
+
return {
|
|
19
|
+
content: [{ type: "text" as const, text: JSON.stringify(payload, null, 2) }],
|
|
20
|
+
details: payload,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Music Tools (SoundChain Agent REST API)
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
function createSearchTool(api: SoundChainApi): AnyAgentTool {
|
|
29
|
+
return {
|
|
30
|
+
name: "soundchain_search",
|
|
31
|
+
label: "SoundChain Search",
|
|
32
|
+
description:
|
|
33
|
+
"Search for music tracks on SoundChain by title, artist, or album. Returns track info including stream URLs, play counts, and SCID codes for streaming rewards.",
|
|
34
|
+
parameters: Type.Object({
|
|
35
|
+
query: Type.String({
|
|
36
|
+
description: "Search query (title, artist, or album). Minimum 2 characters.",
|
|
37
|
+
}),
|
|
38
|
+
limit: Type.Optional(
|
|
39
|
+
Type.Number({ description: "Max results to return (1-50, default 10)." }),
|
|
40
|
+
),
|
|
41
|
+
}),
|
|
42
|
+
async execute(_id: string, params: Record<string, unknown>) {
|
|
43
|
+
const query = typeof params.query === "string" ? params.query.trim() : "";
|
|
44
|
+
if (!query || query.length < 2) {
|
|
45
|
+
return json({ error: "Search query must be at least 2 characters." });
|
|
46
|
+
}
|
|
47
|
+
const limit = typeof params.limit === "number" ? Math.min(Math.max(params.limit, 1), 50) : 10;
|
|
48
|
+
return json(await api.searchTracks(query, limit));
|
|
49
|
+
},
|
|
50
|
+
} as AnyAgentTool;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function createRadioTool(api: SoundChainApi): AnyAgentTool {
|
|
54
|
+
return {
|
|
55
|
+
name: "soundchain_radio",
|
|
56
|
+
label: "OGUN Radio",
|
|
57
|
+
description:
|
|
58
|
+
"Get the currently playing track on OGUN Radio — a decentralized NFT radio station rotating 600+ tracks. Returns now-playing info, stream URL, SCID for rewards, and available genres.",
|
|
59
|
+
parameters: Type.Object({}),
|
|
60
|
+
async execute() {
|
|
61
|
+
return json(await api.getRadio());
|
|
62
|
+
},
|
|
63
|
+
} as AnyAgentTool;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function createPlayTool(api: SoundChainApi): AnyAgentTool {
|
|
67
|
+
return {
|
|
68
|
+
name: "soundchain_play",
|
|
69
|
+
label: "SoundChain Play",
|
|
70
|
+
description:
|
|
71
|
+
"Report a track play on SoundChain and trigger OGUN streaming rewards. Both the creator (70%) and listener (30%) earn OGUN tokens. Include the SCID code to activate rewards.",
|
|
72
|
+
parameters: Type.Object({
|
|
73
|
+
track_id: Type.String({ description: "Track ID from search or radio results." }),
|
|
74
|
+
track_title: Type.String({ description: "Track title for display." }),
|
|
75
|
+
scid: Type.Optional(
|
|
76
|
+
Type.String({
|
|
77
|
+
description: "SCID code (e.g. SC-POL-XXXX-XXXXXXX) to trigger OGUN rewards.",
|
|
78
|
+
}),
|
|
79
|
+
),
|
|
80
|
+
}),
|
|
81
|
+
async execute(_id: string, params: Record<string, unknown>) {
|
|
82
|
+
const trackId = typeof params.track_id === "string" ? params.track_id.trim() : "";
|
|
83
|
+
const trackTitle = typeof params.track_title === "string" ? params.track_title.trim() : "";
|
|
84
|
+
if (!trackId) return json({ error: "track_id is required." });
|
|
85
|
+
if (!trackTitle) return json({ error: "track_title is required." });
|
|
86
|
+
const scid = typeof params.scid === "string" ? params.scid.trim() : undefined;
|
|
87
|
+
return json(await api.reportPlay(trackId, trackTitle, scid));
|
|
88
|
+
},
|
|
89
|
+
} as AnyAgentTool;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function createStatsTool(api: SoundChainApi): AnyAgentTool {
|
|
93
|
+
return {
|
|
94
|
+
name: "soundchain_stats",
|
|
95
|
+
label: "SoundChain Stats",
|
|
96
|
+
description:
|
|
97
|
+
"Get SoundChain platform statistics: total tracks, IPFS-backed audio/artwork counts, NFT counts, and estimated totals.",
|
|
98
|
+
parameters: Type.Object({}),
|
|
99
|
+
async execute() {
|
|
100
|
+
return json(await api.getPlatformStats());
|
|
101
|
+
},
|
|
102
|
+
} as AnyAgentTool;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function createTrendingTool(api: SoundChainApi): AnyAgentTool {
|
|
106
|
+
return {
|
|
107
|
+
name: "soundchain_trending",
|
|
108
|
+
label: "SoundChain Trending",
|
|
109
|
+
description:
|
|
110
|
+
"Get trending content on SoundChain: hot tracks by play count, trending stories/reels, and rising artists by follower count.",
|
|
111
|
+
parameters: Type.Object({}),
|
|
112
|
+
async execute() {
|
|
113
|
+
return json(await api.getTrending());
|
|
114
|
+
},
|
|
115
|
+
} as AnyAgentTool;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function createDiscoverTool(api: SoundChainApi): AnyAgentTool {
|
|
119
|
+
return {
|
|
120
|
+
name: "soundchain_discover",
|
|
121
|
+
label: "SoundChain Discover",
|
|
122
|
+
description:
|
|
123
|
+
"Discover random tracks, posts, and artists on SoundChain. Returns a shuffled mix of content for serendipitous exploration — great for finding new music.",
|
|
124
|
+
parameters: Type.Object({}),
|
|
125
|
+
async execute() {
|
|
126
|
+
return json(await api.getDiscover());
|
|
127
|
+
},
|
|
128
|
+
} as AnyAgentTool;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function createLeaderboardTool(api: SoundChainApi): AnyAgentTool {
|
|
132
|
+
return {
|
|
133
|
+
name: "soundchain_leaderboard",
|
|
134
|
+
label: "SoundChain Leaderboard",
|
|
135
|
+
description:
|
|
136
|
+
"View the SoundChain agent leaderboard: top agents ranked by plays, comments, SCID mints, and OGUN earned. Shows whitelist status and airdrop eligibility.",
|
|
137
|
+
parameters: Type.Object({}),
|
|
138
|
+
async execute() {
|
|
139
|
+
return json(await api.getLeaderboard());
|
|
140
|
+
},
|
|
141
|
+
} as AnyAgentTool;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
// War Room — Infrastructure Tools
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
function createWarRoomHealthTool(wr: WarRoomClient): AnyAgentTool {
|
|
149
|
+
return {
|
|
150
|
+
name: "warroom_health",
|
|
151
|
+
label: "War Room Health",
|
|
152
|
+
description:
|
|
153
|
+
"Check the health of the entire War Room: SCid Worker (task router), Ollama (7 local LLM models), fleet nodes (mini/grater/rog), and specialist agent domains. Returns status of all systems.",
|
|
154
|
+
parameters: Type.Object({}),
|
|
155
|
+
async execute() {
|
|
156
|
+
return json(await wr.fleetHealth());
|
|
157
|
+
},
|
|
158
|
+
} as AnyAgentTool;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function createOllamaDirectTool(wr: WarRoomClient): AnyAgentTool {
|
|
162
|
+
return {
|
|
163
|
+
name: "warroom_ollama",
|
|
164
|
+
label: "War Room Ollama",
|
|
165
|
+
description:
|
|
166
|
+
"Send a prompt directly to any Ollama model. Available: mistral (fast), qwen:7b (code), llama3.1 (reason), falcon:7b (syntax), gemma:7b (deps), mixtral:8x22b (architect, 79GB), jmorgan/grok (deep, 116GB). Zero token cost — all local.",
|
|
167
|
+
parameters: Type.Object({
|
|
168
|
+
model: Type.String({
|
|
169
|
+
description: "Model name (e.g. 'mistral:latest', 'mixtral:8x22b', 'jmorgan/grok:latest').",
|
|
170
|
+
}),
|
|
171
|
+
prompt: Type.String({ description: "The prompt to send to the model." }),
|
|
172
|
+
}),
|
|
173
|
+
async execute(_id: string, params: Record<string, unknown>) {
|
|
174
|
+
const model = typeof params.model === "string" ? params.model.trim() : "";
|
|
175
|
+
const prompt = typeof params.prompt === "string" ? params.prompt.trim() : "";
|
|
176
|
+
if (!model) return json({ error: "model is required." });
|
|
177
|
+
if (!prompt) return json({ error: "prompt is required." });
|
|
178
|
+
return json(await wr.ollamaGenerate(model, prompt));
|
|
179
|
+
},
|
|
180
|
+
} as AnyAgentTool;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function createWarRoomTaskTool(wr: WarRoomClient): AnyAgentTool {
|
|
184
|
+
return {
|
|
185
|
+
name: "warroom_task",
|
|
186
|
+
label: "War Room Task",
|
|
187
|
+
description:
|
|
188
|
+
"Send a raw task to the SCid Worker (localhost:8787). Task prefixes: 'think:', 'code:', 'reason:', 'bash:', 'read:', 'grep:', 'glob:', 'git:status', 'build', 'ping'.",
|
|
189
|
+
parameters: Type.Object({
|
|
190
|
+
task: Type.String({
|
|
191
|
+
description:
|
|
192
|
+
"Task string. Examples: 'bash:ls -la', 'read:src/index.ts', 'grep:TODO:src/', 'build', 'git:status'.",
|
|
193
|
+
}),
|
|
194
|
+
}),
|
|
195
|
+
async execute(_id: string, params: Record<string, unknown>) {
|
|
196
|
+
const task = typeof params.task === "string" ? params.task.trim() : "";
|
|
197
|
+
if (!task) return json({ error: "task is required." });
|
|
198
|
+
return json(await wr.scidTask(task));
|
|
199
|
+
},
|
|
200
|
+
} as AnyAgentTool;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
// Phil Jackson Triangle — Diagnostic Pipeline Tools
|
|
205
|
+
// ---------------------------------------------------------------------------
|
|
206
|
+
|
|
207
|
+
function createDiagnoseTool(wr: WarRoomClient): AnyAgentTool {
|
|
208
|
+
return {
|
|
209
|
+
name: "warroom_diagnose",
|
|
210
|
+
label: "Phil Jackson Diagnose",
|
|
211
|
+
description:
|
|
212
|
+
"Run the Phil Jackson Triangle Pipeline on a problem. Routes through up to 7 Ollama models in sequence — each with a specific role (syntax, deep analysis, fast fix, deps, architect, captain, backup). Auto-classifies the error type and starts with the most relevant model. Use depth='quick' for 1 model, 'standard' for 3, 'deep' for all 7.",
|
|
213
|
+
parameters: Type.Object({
|
|
214
|
+
problem: Type.String({
|
|
215
|
+
description:
|
|
216
|
+
"The error message, build failure, bug description, or code problem to diagnose.",
|
|
217
|
+
}),
|
|
218
|
+
depth: Type.Optional(
|
|
219
|
+
Type.Unsafe<"quick" | "standard" | "deep">({
|
|
220
|
+
type: "string",
|
|
221
|
+
enum: ["quick", "standard", "deep"],
|
|
222
|
+
description:
|
|
223
|
+
"Pipeline depth: 'quick' = 1 model (fast break), 'standard' = 3 models (half-court), 'deep' = all 7 (full triangle rotation). Default: 'standard'.",
|
|
224
|
+
}),
|
|
225
|
+
),
|
|
226
|
+
}),
|
|
227
|
+
async execute(_id: string, params: Record<string, unknown>) {
|
|
228
|
+
const problem = typeof params.problem === "string" ? params.problem.trim() : "";
|
|
229
|
+
if (!problem) return json({ error: "problem is required." });
|
|
230
|
+
|
|
231
|
+
const depth = typeof params.depth === "string" ? params.depth : "standard";
|
|
232
|
+
let result;
|
|
233
|
+
switch (depth) {
|
|
234
|
+
case "quick":
|
|
235
|
+
result = await quickDiagnose(wr, problem);
|
|
236
|
+
break;
|
|
237
|
+
case "deep":
|
|
238
|
+
result = await deepDiagnose(wr, problem);
|
|
239
|
+
break;
|
|
240
|
+
default:
|
|
241
|
+
result = await runPipeline(wr, problem, 3);
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
return json(result);
|
|
245
|
+
},
|
|
246
|
+
} as AnyAgentTool;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function createSpecialistTool(wr: WarRoomClient): AnyAgentTool {
|
|
250
|
+
return {
|
|
251
|
+
name: "warroom_specialist",
|
|
252
|
+
label: "War Room Specialist",
|
|
253
|
+
description: `Route a problem to a SoundChain specialist domain. Available specialists:
|
|
254
|
+
- code-simplifier: Cleanup, refactoring, duplicate consolidation
|
|
255
|
+
- dex-inspector: DEX swap flow, marketplace tx, OGUN swap, auction, staking
|
|
256
|
+
- helix-validator: MongoDB <-> Blockchain sync, ownership mismatch, balance desync, SCid
|
|
257
|
+
- ipfs-auditor: IPFS/Pinata streaming, CID missing, gateway timeout, artwork
|
|
258
|
+
- mobile-detective: iOS/Android bugs, Safari, wallet deep links, in-app browser
|
|
259
|
+
- verify-app: E2E testing, pre-merge checks, regression tests
|
|
260
|
+
- wallet-debugger: Wallet connection, balance shows 0, session lost, chain switch`,
|
|
261
|
+
parameters: Type.Object({
|
|
262
|
+
specialist: Type.String({
|
|
263
|
+
description:
|
|
264
|
+
"Specialist domain: 'code-simplifier', 'dex-inspector', 'helix-validator', 'ipfs-auditor', 'mobile-detective', 'verify-app', or 'wallet-debugger'.",
|
|
265
|
+
}),
|
|
266
|
+
problem: Type.String({
|
|
267
|
+
description: "Describe the issue for the specialist to investigate.",
|
|
268
|
+
}),
|
|
269
|
+
}),
|
|
270
|
+
async execute(_id: string, params: Record<string, unknown>) {
|
|
271
|
+
const specialist = typeof params.specialist === "string" ? params.specialist.trim() : "";
|
|
272
|
+
const problem = typeof params.problem === "string" ? params.problem.trim() : "";
|
|
273
|
+
if (!specialist) return json({ error: "specialist is required." });
|
|
274
|
+
if (!problem) return json({ error: "problem is required." });
|
|
275
|
+
|
|
276
|
+
const spec = SPECIALISTS[specialist as keyof typeof SPECIALISTS];
|
|
277
|
+
if (!spec) {
|
|
278
|
+
return json({
|
|
279
|
+
error: `Unknown specialist '${specialist}'. Available: ${Object.keys(SPECIALISTS).join(", ")}`,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Route to the best model for this specialist's domain
|
|
284
|
+
const modelMap: Record<string, string> = {
|
|
285
|
+
"code-simplifier": "qwen:7b",
|
|
286
|
+
"dex-inspector": "llama3.1:latest",
|
|
287
|
+
"helix-validator": "jmorgan/grok:latest",
|
|
288
|
+
"ipfs-auditor": "mistral:latest",
|
|
289
|
+
"mobile-detective": "gemma:7b",
|
|
290
|
+
"verify-app": "mistral:latest",
|
|
291
|
+
"wallet-debugger": "llama3.1:latest",
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
const model = modelMap[specialist] ?? "mistral:latest";
|
|
295
|
+
|
|
296
|
+
const prompt = `You are the ${specialist} specialist for SoundChain.
|
|
297
|
+
Your focus: ${spec.focus}
|
|
298
|
+
Known triggers: ${spec.triggers.join(", ")}
|
|
299
|
+
|
|
300
|
+
PROBLEM:
|
|
301
|
+
${problem}
|
|
302
|
+
|
|
303
|
+
Analyze this issue within your domain. Be specific: file paths, line numbers, code fixes, commands to run. No fluff.`;
|
|
304
|
+
|
|
305
|
+
const response = await wr.ollamaGenerate(model, prompt);
|
|
306
|
+
return json({
|
|
307
|
+
specialist,
|
|
308
|
+
focus: spec.focus,
|
|
309
|
+
model,
|
|
310
|
+
response,
|
|
311
|
+
});
|
|
312
|
+
},
|
|
313
|
+
} as AnyAgentTool;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function createPipelineInfoTool(): AnyAgentTool {
|
|
317
|
+
return {
|
|
318
|
+
name: "warroom_roster",
|
|
319
|
+
label: "War Room Roster",
|
|
320
|
+
description:
|
|
321
|
+
"View the Phil Jackson Triangle Pipeline roster — all 7 models, their roles, triggers, tasks, and NBA chemistry analogies. Also shows fleet nodes and specialist domains.",
|
|
322
|
+
parameters: Type.Object({}),
|
|
323
|
+
async execute() {
|
|
324
|
+
return json({
|
|
325
|
+
pipeline: PIPELINE_STAGES.map((s) => ({
|
|
326
|
+
stage: s.name,
|
|
327
|
+
model: s.model,
|
|
328
|
+
role: s.role,
|
|
329
|
+
trigger: s.trigger,
|
|
330
|
+
tasks: s.tasks,
|
|
331
|
+
chemistry: s.chemistry,
|
|
332
|
+
})),
|
|
333
|
+
models: OLLAMA_MODELS,
|
|
334
|
+
fleet: FLEET_NODES,
|
|
335
|
+
specialists: SPECIALISTS,
|
|
336
|
+
});
|
|
337
|
+
},
|
|
338
|
+
} as AnyAgentTool;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// ---------------------------------------------------------------------------
|
|
342
|
+
// Plugin entry point
|
|
343
|
+
// ---------------------------------------------------------------------------
|
|
344
|
+
|
|
345
|
+
const plugin = {
|
|
346
|
+
id: "soundchain",
|
|
347
|
+
name: "SoundChain",
|
|
348
|
+
description:
|
|
349
|
+
"SoundChain War Room — Phil Jackson Triangle diagnostic pipeline (7 Ollama models), specialist agents, fleet nodes, music API + OGUN streaming rewards. Build. Diagnose. Ship.",
|
|
350
|
+
|
|
351
|
+
register(api: OpenClawPluginApi) {
|
|
352
|
+
console.log("[SoundChain] register() called — registering tools + channel");
|
|
353
|
+
// Store runtime for inbound message forwarding (mirrors Nostr pattern)
|
|
354
|
+
setSoundChainRuntime(api.runtime);
|
|
355
|
+
|
|
356
|
+
const cfg = (api.pluginConfig ?? {}) as Record<string, unknown>;
|
|
357
|
+
|
|
358
|
+
// Music API config
|
|
359
|
+
const scConfig: SoundChainConfig = {
|
|
360
|
+
apiUrl: typeof cfg.apiUrl === "string" && cfg.apiUrl ? cfg.apiUrl : "https://soundchain.io",
|
|
361
|
+
agentName:
|
|
362
|
+
typeof cfg.agentName === "string" && cfg.agentName ? cfg.agentName : "openclaw-agent",
|
|
363
|
+
agentWallet:
|
|
364
|
+
typeof cfg.agentWallet === "string" && cfg.agentWallet ? cfg.agentWallet : undefined,
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
// War Room config
|
|
368
|
+
const wrConfig: WarRoomConfig = {
|
|
369
|
+
scidWorkerUrl:
|
|
370
|
+
typeof cfg.scidWorkerUrl === "string" && cfg.scidWorkerUrl
|
|
371
|
+
? cfg.scidWorkerUrl
|
|
372
|
+
: "http://localhost:8787",
|
|
373
|
+
ollamaUrl:
|
|
374
|
+
typeof cfg.ollamaUrl === "string" && cfg.ollamaUrl
|
|
375
|
+
? cfg.ollamaUrl
|
|
376
|
+
: "http://localhost:11434",
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
const scApi = createSoundChainApi(scConfig);
|
|
380
|
+
const wrClient = createWarRoomClient(wrConfig);
|
|
381
|
+
|
|
382
|
+
// --- Music tools (SoundChain Agent REST API) ---
|
|
383
|
+
// Register all music tools in a single call with explicit names
|
|
384
|
+
// so the gateway exposes them to the agent session.
|
|
385
|
+
const musicToolNames = [
|
|
386
|
+
"soundchain_search",
|
|
387
|
+
"soundchain_radio",
|
|
388
|
+
"soundchain_play",
|
|
389
|
+
"soundchain_stats",
|
|
390
|
+
"soundchain_trending",
|
|
391
|
+
"soundchain_discover",
|
|
392
|
+
"soundchain_leaderboard",
|
|
393
|
+
];
|
|
394
|
+
const musicToolFactories = [
|
|
395
|
+
createSearchTool,
|
|
396
|
+
createRadioTool,
|
|
397
|
+
createPlayTool,
|
|
398
|
+
createStatsTool,
|
|
399
|
+
createTrendingTool,
|
|
400
|
+
createDiscoverTool,
|
|
401
|
+
createLeaderboardTool,
|
|
402
|
+
];
|
|
403
|
+
|
|
404
|
+
api.registerTool(
|
|
405
|
+
((ctx) => {
|
|
406
|
+
if (ctx.sandboxed) return null;
|
|
407
|
+
const tools = musicToolFactories.map((f) => f(scApi));
|
|
408
|
+
return tools;
|
|
409
|
+
}) as OpenClawPluginToolFactory,
|
|
410
|
+
{ names: musicToolNames },
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
// --- War Room + Diagnostic + Roster tools ---
|
|
414
|
+
api.registerTool(
|
|
415
|
+
((ctx) => {
|
|
416
|
+
if (ctx.sandboxed) return null;
|
|
417
|
+
return [
|
|
418
|
+
createWarRoomHealthTool(wrClient),
|
|
419
|
+
createOllamaDirectTool(wrClient),
|
|
420
|
+
createWarRoomTaskTool(wrClient),
|
|
421
|
+
createDiagnoseTool(wrClient),
|
|
422
|
+
createSpecialistTool(wrClient),
|
|
423
|
+
createPipelineInfoTool(),
|
|
424
|
+
];
|
|
425
|
+
}) as OpenClawPluginToolFactory,
|
|
426
|
+
{
|
|
427
|
+
names: [
|
|
428
|
+
"warroom_health",
|
|
429
|
+
"warroom_ollama",
|
|
430
|
+
"warroom_task",
|
|
431
|
+
"warroom_diagnose",
|
|
432
|
+
"warroom_specialist",
|
|
433
|
+
"warroom_roster",
|
|
434
|
+
],
|
|
435
|
+
},
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
// --- Codebase Summarizer Tool ---
|
|
439
|
+
api.registerTool(
|
|
440
|
+
((ctx) => {
|
|
441
|
+
if (ctx.sandboxed) return null;
|
|
442
|
+
return {
|
|
443
|
+
name: "soundchain_platform_docs" as const,
|
|
444
|
+
description:
|
|
445
|
+
"Fetch SoundChain platform documentation (skill.md) — API endpoints, architecture, and agent gateway capabilities.",
|
|
446
|
+
parameters: Type.Object({}),
|
|
447
|
+
label: "SoundChain Discovery",
|
|
448
|
+
async execute() {
|
|
449
|
+
try {
|
|
450
|
+
const res = await fetch("https://soundchain.io/skill.md", {
|
|
451
|
+
signal: AbortSignal.timeout(15_000),
|
|
452
|
+
});
|
|
453
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
454
|
+
const text = await res.text();
|
|
455
|
+
return json({
|
|
456
|
+
platform: "SoundChain",
|
|
457
|
+
skillVersion: "2.0",
|
|
458
|
+
content: text.slice(0, 12000),
|
|
459
|
+
});
|
|
460
|
+
} catch (err) {
|
|
461
|
+
return json({ error: "Failed to fetch skill.md", message: String(err) });
|
|
462
|
+
}
|
|
463
|
+
},
|
|
464
|
+
};
|
|
465
|
+
}) as OpenClawPluginToolFactory,
|
|
466
|
+
{ names: ["soundchain_platform_docs"] },
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
// --- SoundChain messaging channel ---
|
|
470
|
+
// Registers SoundChain as an OpenClaw messaging channel.
|
|
471
|
+
// Configure with channels.soundchain.apiToken in openclaw config.
|
|
472
|
+
// Enables: outbound DMs to SoundChain users, inbound message polling,
|
|
473
|
+
// and future cross-channel routing (WhatsApp/Telegram/Signal via OpenClaw).
|
|
474
|
+
api.registerChannel({ plugin: soundchainChannelPlugin });
|
|
475
|
+
},
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
export default plugin;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "soundchain",
|
|
3
|
+
"name": "SoundChain",
|
|
4
|
+
"channels": ["soundchain"],
|
|
5
|
+
"description": "Search music, play tracks, earn OGUN streaming rewards, send DMs via SoundChain messaging channel, and command the War Room — 7 Ollama models, fleet nodes, and SCid Worker.",
|
|
6
|
+
"configSchema": {
|
|
7
|
+
"type": "object",
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"apiUrl": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"description": "SoundChain API base URL"
|
|
13
|
+
},
|
|
14
|
+
"agentName": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"description": "Agent identity for play tracking"
|
|
17
|
+
},
|
|
18
|
+
"agentWallet": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"description": "Polygon wallet address for OGUN listener rewards"
|
|
21
|
+
},
|
|
22
|
+
"scidWorkerUrl": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"description": "SCid Worker URL (task router for Ollama models)"
|
|
25
|
+
},
|
|
26
|
+
"ollamaUrl": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"description": "Ollama API URL (local LLM inference)"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"uiHints": {
|
|
33
|
+
"apiUrl": {
|
|
34
|
+
"label": "API URL",
|
|
35
|
+
"placeholder": "https://soundchain.io"
|
|
36
|
+
},
|
|
37
|
+
"agentName": {
|
|
38
|
+
"label": "Agent Name",
|
|
39
|
+
"placeholder": "my-agent"
|
|
40
|
+
},
|
|
41
|
+
"agentWallet": {
|
|
42
|
+
"label": "OGUN Wallet",
|
|
43
|
+
"placeholder": "0x...",
|
|
44
|
+
"help": "Polygon wallet to receive listener rewards"
|
|
45
|
+
},
|
|
46
|
+
"scidWorkerUrl": {
|
|
47
|
+
"label": "SCid Worker URL",
|
|
48
|
+
"placeholder": "http://localhost:8787",
|
|
49
|
+
"help": "Task router for War Room Ollama models"
|
|
50
|
+
},
|
|
51
|
+
"ollamaUrl": {
|
|
52
|
+
"label": "Ollama URL",
|
|
53
|
+
"placeholder": "http://localhost:11434",
|
|
54
|
+
"help": "Local Ollama API for zero-cost LLM inference"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@soundchain/openclaw-plugin",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "SoundChain plugin for OpenClaw — music search, OGUN streaming rewards, War Room diagnostics, Phil Jackson Triangle pipeline, and Pulse messaging channel.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"index.ts",
|
|
9
|
+
"runtime-api.ts",
|
|
10
|
+
"openclaw.plugin.json",
|
|
11
|
+
"src/"
|
|
12
|
+
],
|
|
13
|
+
"keywords": [
|
|
14
|
+
"openclaw",
|
|
15
|
+
"openclaw-plugin",
|
|
16
|
+
"soundchain",
|
|
17
|
+
"music",
|
|
18
|
+
"ogun",
|
|
19
|
+
"web3",
|
|
20
|
+
"streaming",
|
|
21
|
+
"ai-agent"
|
|
22
|
+
],
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "https://github.com/soundchainio/openclaw-soundchain-plugin.git"
|
|
26
|
+
},
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@sinclair/typebox": "0.34.48"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"openclaw": ">=2026.3.0"
|
|
33
|
+
},
|
|
34
|
+
"openclaw": {
|
|
35
|
+
"extensions": [
|
|
36
|
+
"./index.ts"
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
}
|
package/runtime-api.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type { OpenClawPluginApi, AnyAgentTool, ChannelPlugin } from "openclaw/plugin-sdk/core";
|
|
2
|
+
export { definePluginEntry } from "openclaw/plugin-sdk/core";
|
|
3
|
+
|
|
4
|
+
// Re-export tool factory type (not in core, but used for tool registration)
|
|
5
|
+
// Inlined from plugins/types.ts to avoid cross-package import
|
|
6
|
+
export type OpenClawPluginToolFactory = (ctx: {
|
|
7
|
+
sandboxed: boolean;
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
}) => unknown | null;
|