@chorus-aidlc/chorus-openclaw-plugin 0.2.2 → 0.2.4
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/openclaw.plugin.json +0 -1
- package/package.json +1 -1
- package/src/config.ts +32 -0
- package/src/index.ts +13 -10
- package/src/tools/admin-tools.ts +41 -0
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
package/src/config.ts
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
|
+
export const CONFIG_FILE_PATH = "~/.openclaw/openclaw.json";
|
|
4
|
+
export const CONFIG_KEY_PATH = "plugins.entries.chorus-openclaw-plugin.config";
|
|
5
|
+
|
|
3
6
|
export const chorusConfigSchema = z.object({
|
|
4
7
|
chorusUrl: z
|
|
5
8
|
.string()
|
|
6
9
|
.url()
|
|
10
|
+
.optional()
|
|
7
11
|
.describe("Chorus server URL (e.g. https://chorus.example.com)"),
|
|
8
12
|
apiKey: z
|
|
9
13
|
.string()
|
|
10
14
|
.startsWith("cho_")
|
|
15
|
+
.optional()
|
|
11
16
|
.describe("Chorus API Key (cho_ prefix)"),
|
|
12
17
|
projectUuids: z
|
|
13
18
|
.array(z.string().uuid())
|
|
@@ -22,3 +27,30 @@ export const chorusConfigSchema = z.object({
|
|
|
22
27
|
});
|
|
23
28
|
|
|
24
29
|
export type ChorusPluginConfig = z.infer<typeof chorusConfigSchema>;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Check required config fields and warn about missing ones.
|
|
33
|
+
* Returns true if all required fields are present, false otherwise.
|
|
34
|
+
*/
|
|
35
|
+
export function validateConfigWithWarnings(
|
|
36
|
+
config: ChorusPluginConfig,
|
|
37
|
+
logger: { warn: (msg: string) => void },
|
|
38
|
+
): boolean {
|
|
39
|
+
const missing: string[] = [];
|
|
40
|
+
|
|
41
|
+
if (!config.chorusUrl) {
|
|
42
|
+
missing.push(` - "chorusUrl": set at ${CONFIG_KEY_PATH}.chorusUrl in ${CONFIG_FILE_PATH}`);
|
|
43
|
+
}
|
|
44
|
+
if (!config.apiKey) {
|
|
45
|
+
missing.push(` - "apiKey": set at ${CONFIG_KEY_PATH}.apiKey in ${CONFIG_FILE_PATH}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (missing.length > 0) {
|
|
49
|
+
logger.warn(
|
|
50
|
+
`[Chorus] Plugin is missing required configuration. Features will be disabled until configured:\n` +
|
|
51
|
+
missing.join("\n")
|
|
52
|
+
);
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2
2
|
type OpenClawPluginApi = any;
|
|
3
3
|
|
|
4
|
-
import { chorusConfigSchema, type ChorusPluginConfig } from "./config.js";
|
|
4
|
+
import { chorusConfigSchema, type ChorusPluginConfig, validateConfigWithWarnings } from "./config.js";
|
|
5
5
|
import { ChorusMcpClient } from "./mcp-client.js";
|
|
6
6
|
import { ChorusSseListener } from "./sse-listener.js";
|
|
7
7
|
import { ChorusEventRouter } from "./event-router.js";
|
|
@@ -51,31 +51,34 @@ const plugin = {
|
|
|
51
51
|
register(api: OpenClawPluginApi) {
|
|
52
52
|
const rawConfig = api.pluginConfig ?? {};
|
|
53
53
|
const config: ChorusPluginConfig = {
|
|
54
|
-
chorusUrl: rawConfig.chorusUrl
|
|
55
|
-
apiKey: rawConfig.apiKey
|
|
54
|
+
chorusUrl: rawConfig.chorusUrl || undefined,
|
|
55
|
+
apiKey: rawConfig.apiKey || undefined,
|
|
56
56
|
projectUuids: rawConfig.projectUuids ?? [],
|
|
57
57
|
autoStart: rawConfig.autoStart ?? true,
|
|
58
58
|
};
|
|
59
59
|
const logger = api.logger;
|
|
60
60
|
|
|
61
|
-
if (!config
|
|
62
|
-
logger.error("Chorus plugin missing required config: chorusUrl and apiKey");
|
|
61
|
+
if (!validateConfigWithWarnings(config, logger)) {
|
|
63
62
|
return;
|
|
64
63
|
}
|
|
65
64
|
|
|
65
|
+
// After validateConfigWithWarnings, chorusUrl and apiKey are guaranteed present
|
|
66
|
+
const chorusUrl = config.chorusUrl!;
|
|
67
|
+
const apiKey = config.apiKey!;
|
|
68
|
+
|
|
66
69
|
// Resolve gateway URL and hooks token from OpenClaw config
|
|
67
70
|
const gatewayPort = api.config?.gateway?.port ?? 18789;
|
|
68
71
|
const gatewayUrl = `http://127.0.0.1:${gatewayPort}`;
|
|
69
72
|
const hooksToken = api.config?.hooks?.token ?? "";
|
|
70
73
|
|
|
71
74
|
logger.info(
|
|
72
|
-
`Chorus plugin initializing — ${
|
|
75
|
+
`Chorus plugin initializing — ${chorusUrl} (${config.projectUuids?.length || "all"} projects)`
|
|
73
76
|
);
|
|
74
77
|
|
|
75
78
|
// --- MCP Client ---
|
|
76
79
|
const mcpClient = new ChorusMcpClient({
|
|
77
|
-
chorusUrl
|
|
78
|
-
apiKey
|
|
80
|
+
chorusUrl,
|
|
81
|
+
apiKey,
|
|
79
82
|
logger,
|
|
80
83
|
});
|
|
81
84
|
|
|
@@ -103,8 +106,8 @@ const plugin = {
|
|
|
103
106
|
id: "chorus-sse",
|
|
104
107
|
async start() {
|
|
105
108
|
sseListener = new ChorusSseListener({
|
|
106
|
-
chorusUrl
|
|
107
|
-
apiKey
|
|
109
|
+
chorusUrl,
|
|
110
|
+
apiKey,
|
|
108
111
|
logger,
|
|
109
112
|
onEvent: (event) => eventRouter.dispatch(event),
|
|
110
113
|
onReconnect: async () => {
|
package/src/tools/admin-tools.ts
CHANGED
|
@@ -44,6 +44,47 @@ export function registerAdminTools(api: any, mcpClient: ChorusMcpClient) {
|
|
|
44
44
|
},
|
|
45
45
|
});
|
|
46
46
|
|
|
47
|
+
api.registerTool({
|
|
48
|
+
name: "chorus_admin_approve_proposal",
|
|
49
|
+
description:
|
|
50
|
+
"Approve a Proposal (Admin exclusive). On approval, documentDrafts and taskDrafts are automatically materialized into real Document and Task entities — materialized Tasks can then be claimed and executed by agents. " +
|
|
51
|
+
"⚠️ This action is irreversible — unless there is a special reason, you MUST obtain explicit human approval before calling this tool.",
|
|
52
|
+
parameters: {
|
|
53
|
+
type: "object",
|
|
54
|
+
properties: {
|
|
55
|
+
proposalUuid: { type: "string", description: "Proposal UUID" },
|
|
56
|
+
reviewNote: { type: "string", description: "Optional review note" },
|
|
57
|
+
},
|
|
58
|
+
required: ["proposalUuid"],
|
|
59
|
+
additionalProperties: false,
|
|
60
|
+
},
|
|
61
|
+
async execute(_id: string, { proposalUuid, reviewNote }: { proposalUuid: string; reviewNote?: string }) {
|
|
62
|
+
const args: Record<string, unknown> = { proposalUuid };
|
|
63
|
+
if (reviewNote) args.reviewNote = reviewNote;
|
|
64
|
+
const result = await mcpClient.callTool("chorus_admin_approve_proposal", args);
|
|
65
|
+
return JSON.stringify(result, null, 2);
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
api.registerTool({
|
|
70
|
+
name: "chorus_admin_verify_task",
|
|
71
|
+
description:
|
|
72
|
+
"Verify a Task (to_verify → done, Admin exclusive). Marks a task as completed after verification. Downstream tasks that depend on this task will only be unblocked after it is verified. " +
|
|
73
|
+
"⚠️ This action is irreversible — unless there is a special reason, you MUST obtain explicit human approval before calling this tool.",
|
|
74
|
+
parameters: {
|
|
75
|
+
type: "object",
|
|
76
|
+
properties: {
|
|
77
|
+
taskUuid: { type: "string", description: "Task UUID" },
|
|
78
|
+
},
|
|
79
|
+
required: ["taskUuid"],
|
|
80
|
+
additionalProperties: false,
|
|
81
|
+
},
|
|
82
|
+
async execute(_id: string, { taskUuid }: { taskUuid: string }) {
|
|
83
|
+
const result = await mcpClient.callTool("chorus_admin_verify_task", { taskUuid });
|
|
84
|
+
return JSON.stringify(result, null, 2);
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
|
|
47
88
|
api.registerTool({
|
|
48
89
|
name: "chorus_mark_acceptance_criteria",
|
|
49
90
|
description: "Mark acceptance criteria as passed or failed (admin verification). Blocked criteria prevent task from being verified (to_verify -> done).",
|