@chorus-aidlc/chorus-openclaw-plugin 0.3.1 → 0.5.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 +218 -218
- 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 +14 -12
- package/package.json +24 -5
- package/skills/brainstorm/SKILL.md +163 -0
- package/skills/chorus/SKILL.md +413 -0
- package/skills/develop/SKILL.md +434 -0
- package/skills/idea/SKILL.md +293 -0
- package/skills/openspec-aware/SKILL.md +425 -0
- package/skills/proposal/SKILL.md +397 -0
- package/skills/proposal-reviewer/SKILL.md +117 -0
- package/skills/quick-dev/SKILL.md +198 -0
- package/skills/review/SKILL.md +354 -0
- package/skills/task-reviewer/SKILL.md +113 -0
- package/skills/yolo/SKILL.md +498 -0
- package/src/commands.ts +147 -57
- 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 -117
- package/src/tools/common-tools.ts +0 -546
- package/src/tools/dev-tools.ts +0 -97
- package/src/tools/pm-tools.ts +0 -390
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export type McpClientStatus = "disconnected" | "connecting" | "connected" | "reconnecting";
|
|
2
|
+
export interface ChorusMcpClientOptions {
|
|
3
|
+
chorusUrl: string;
|
|
4
|
+
apiKey: string;
|
|
5
|
+
logger: {
|
|
6
|
+
info: (msg: string) => void;
|
|
7
|
+
warn: (msg: string) => void;
|
|
8
|
+
error: (msg: string) => void;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Wraps @modelcontextprotocol/sdk Client with:
|
|
13
|
+
* - Lazy connection (connect on first callTool)
|
|
14
|
+
* - Auto-reconnect on session expiry (404)
|
|
15
|
+
* - Status tracking
|
|
16
|
+
* - Graceful disconnect
|
|
17
|
+
*/
|
|
18
|
+
export declare class ChorusMcpClient {
|
|
19
|
+
private client;
|
|
20
|
+
private transport;
|
|
21
|
+
private _status;
|
|
22
|
+
private readonly opts;
|
|
23
|
+
constructor(opts: ChorusMcpClientOptions);
|
|
24
|
+
get status(): McpClientStatus;
|
|
25
|
+
/** Establish MCP connection. Called lazily on first callTool. */
|
|
26
|
+
connect(): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Call a Chorus MCP tool. Handles lazy connection and auto-reconnect.
|
|
29
|
+
* Returns parsed JSON from the first text content block.
|
|
30
|
+
*/
|
|
31
|
+
callTool(name: string, args?: Record<string, unknown>): Promise<unknown>;
|
|
32
|
+
/** Graceful disconnect. */
|
|
33
|
+
disconnect(): Promise<void>;
|
|
34
|
+
private _doCallTool;
|
|
35
|
+
private isSessionExpiredError;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=mcp-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-client.d.ts","sourceRoot":"","sources":["../src/mcp-client.ts"],"names":[],"mappings":"AAoBA,MAAM,MAAM,eAAe,GAAG,cAAc,GAAG,YAAY,GAAG,WAAW,GAAG,cAAc,CAAC;AAE3F,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE;QAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAAC,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAAC,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CAAC;CACpG;AAED;;;;;;GAMG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,SAAS,CAA8C;IAC/D,OAAO,CAAC,OAAO,CAAmC;IAClD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAyB;gBAElC,IAAI,EAAE,sBAAsB;IAIxC,IAAI,MAAM,IAAI,eAAe,CAE5B;IAED,iEAAiE;IAC3D,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAgC9B;;;OAGG;IACG,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAsBlF,2BAA2B;IACrB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;YAcnB,WAAW;IA2BzB,OAAO,CAAC,qBAAqB;CAO9B"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin-INTERNAL MCP client.
|
|
3
|
+
*
|
|
4
|
+
* This is NOT how the agent gets Chorus tools — those are exposed natively by
|
|
5
|
+
* OpenClaw connecting to the `mcp.servers.chorus` entry written by
|
|
6
|
+
* `ensureChorusMcpServer` (see mcp-registration.ts). The plugin no longer
|
|
7
|
+
* hand-wraps any Chorus tool with `api.registerTool`.
|
|
8
|
+
*
|
|
9
|
+
* This client exists only for the plugin's OWN synchronous calls into Chorus:
|
|
10
|
+
* - `/chorus` command: chorus_checkin, chorus_get_my_assignments,
|
|
11
|
+
* chorus_get_available_ideas (see commands.ts)
|
|
12
|
+
* - SSE reconnect back-fill: chorus_get_notifications (see index.ts)
|
|
13
|
+
*
|
|
14
|
+
* No agent-facing tool depends on it. Keep it a generic StreamableHTTP
|
|
15
|
+
* `callTool` client (lazy connect + 404 reconnect); do not grow it into a tool
|
|
16
|
+
* surface.
|
|
17
|
+
*/
|
|
18
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
19
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
20
|
+
/**
|
|
21
|
+
* Wraps @modelcontextprotocol/sdk Client with:
|
|
22
|
+
* - Lazy connection (connect on first callTool)
|
|
23
|
+
* - Auto-reconnect on session expiry (404)
|
|
24
|
+
* - Status tracking
|
|
25
|
+
* - Graceful disconnect
|
|
26
|
+
*/
|
|
27
|
+
export class ChorusMcpClient {
|
|
28
|
+
client = null;
|
|
29
|
+
transport = null;
|
|
30
|
+
_status = "disconnected";
|
|
31
|
+
opts;
|
|
32
|
+
constructor(opts) {
|
|
33
|
+
this.opts = opts;
|
|
34
|
+
}
|
|
35
|
+
get status() {
|
|
36
|
+
return this._status;
|
|
37
|
+
}
|
|
38
|
+
/** Establish MCP connection. Called lazily on first callTool. */
|
|
39
|
+
async connect() {
|
|
40
|
+
if (this._status === "connected" && this.client)
|
|
41
|
+
return;
|
|
42
|
+
this._status = "connecting";
|
|
43
|
+
try {
|
|
44
|
+
this.transport = new StreamableHTTPClientTransport(new URL("/api/mcp", this.opts.chorusUrl), {
|
|
45
|
+
requestInit: {
|
|
46
|
+
headers: {
|
|
47
|
+
Authorization: `Bearer ${this.opts.apiKey}`,
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
this.client = new Client({
|
|
52
|
+
name: "openclaw-chorus",
|
|
53
|
+
version: "0.1.0",
|
|
54
|
+
});
|
|
55
|
+
await this.client.connect(this.transport);
|
|
56
|
+
this._status = "connected";
|
|
57
|
+
this.opts.logger.info("MCP connection established");
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
this._status = "disconnected";
|
|
61
|
+
this.client = null;
|
|
62
|
+
this.transport = null;
|
|
63
|
+
throw err;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Call a Chorus MCP tool. Handles lazy connection and auto-reconnect.
|
|
68
|
+
* Returns parsed JSON from the first text content block.
|
|
69
|
+
*/
|
|
70
|
+
async callTool(name, args = {}) {
|
|
71
|
+
// Lazy connect
|
|
72
|
+
if (!this.client || this._status !== "connected") {
|
|
73
|
+
await this.connect();
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
return await this._doCallTool(name, args);
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
// Session expired (404) or connection lost — reconnect and retry once
|
|
80
|
+
if (this.isSessionExpiredError(err)) {
|
|
81
|
+
this.opts.logger.warn("MCP session expired, reconnecting...");
|
|
82
|
+
this._status = "reconnecting";
|
|
83
|
+
this.client = null;
|
|
84
|
+
this.transport = null;
|
|
85
|
+
await this.connect();
|
|
86
|
+
return await this._doCallTool(name, args);
|
|
87
|
+
}
|
|
88
|
+
throw err;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/** Graceful disconnect. */
|
|
92
|
+
async disconnect() {
|
|
93
|
+
if (this.client) {
|
|
94
|
+
try {
|
|
95
|
+
await this.client.close();
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// Ignore close errors
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
this.client = null;
|
|
102
|
+
this.transport = null;
|
|
103
|
+
this._status = "disconnected";
|
|
104
|
+
this.opts.logger.info("MCP connection closed");
|
|
105
|
+
}
|
|
106
|
+
async _doCallTool(name, args) {
|
|
107
|
+
if (!this.client)
|
|
108
|
+
throw new Error("MCP client not connected");
|
|
109
|
+
const result = await this.client.callTool({ name, arguments: args });
|
|
110
|
+
if (result.isError) {
|
|
111
|
+
const errorText = result.content
|
|
112
|
+
.filter((c) => c.type === "text")
|
|
113
|
+
.map((c) => c.text)
|
|
114
|
+
.join("\n");
|
|
115
|
+
throw new Error(`Chorus MCP tool error (${name}): ${errorText}`);
|
|
116
|
+
}
|
|
117
|
+
// Parse first text content block as JSON
|
|
118
|
+
const textContent = result.content.find((c) => c.type === "text");
|
|
119
|
+
if (!textContent?.text)
|
|
120
|
+
return null;
|
|
121
|
+
try {
|
|
122
|
+
return JSON.parse(textContent.text);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// Return raw text if not valid JSON
|
|
126
|
+
return textContent.text;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
isSessionExpiredError(err) {
|
|
130
|
+
if (err instanceof Error) {
|
|
131
|
+
const msg = err.message.toLowerCase();
|
|
132
|
+
return msg.includes("404") || msg.includes("session") || msg.includes("not found");
|
|
133
|
+
}
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=mcp-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-client.js","sourceRoot":"","sources":["../src/mcp-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
|
|
2
|
+
import type { ChorusPluginConfig } from "./config.js";
|
|
3
|
+
/**
|
|
4
|
+
* Ensure the Chorus MCP server is registered with OpenClaw so the agent gains
|
|
5
|
+
* the native `chorus__*` tools.
|
|
6
|
+
*
|
|
7
|
+
* Writes (idempotently) an `mcp.servers.chorus` entry into the OpenClaw config
|
|
8
|
+
* via `api.runtime.config.mutateConfigFile` — a remote streamable-http MCP
|
|
9
|
+
* server with Bearer auth. OpenClaw then connects to the remote Chorus MCP
|
|
10
|
+
* server on (re)load and auto-exposes its tools under the `chorus__` prefix; we
|
|
11
|
+
* never re-declare those tools with `api.registerTool`.
|
|
12
|
+
*
|
|
13
|
+
* Behavior:
|
|
14
|
+
* - Missing `chorusUrl`/`apiKey`: do NOT write; warn naming the missing field(s).
|
|
15
|
+
* - Existing entry already equals the desired entry: return without writing
|
|
16
|
+
* (idempotent — no config reload triggered).
|
|
17
|
+
* - Otherwise: `mutateConfigFile({ afterWrite: { mode: "auto" }, mutate })`,
|
|
18
|
+
* mutating the draft in place (OpenClaw's `mutate` callback receives a
|
|
19
|
+
* structuredClone draft and persists the in-place mutation).
|
|
20
|
+
* - Any rejection from `mutateConfigFile` (or the runtime API being absent) is
|
|
21
|
+
* logged at error level and swallowed — registration failure must never crash
|
|
22
|
+
* the gateway, so the SSE service and `/chorus` command still register.
|
|
23
|
+
*/
|
|
24
|
+
export declare function ensureChorusMcpServer(api: OpenClawPluginApi, cfg: ChorusPluginConfig): Promise<void>;
|
|
25
|
+
//# sourceMappingURL=mcp-registration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-registration.d.ts","sourceRoot":"","sources":["../src/mcp-registration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAC1E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AA8CtD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,iBAAiB,EACtB,GAAG,EAAE,kBAAkB,GACtB,OAAO,CAAC,IAAI,CAAC,CAsEf"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the desired `mcp.servers.chorus` entry for the current config.
|
|
3
|
+
*/
|
|
4
|
+
function buildDesiredEntry(chorusUrl, apiKey) {
|
|
5
|
+
return {
|
|
6
|
+
url: new URL("/api/mcp", chorusUrl).toString(),
|
|
7
|
+
transport: "streamable-http",
|
|
8
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Shallow-equal the fields that define the desired entry. We only compare the
|
|
13
|
+
* three load-bearing fields (url, transport, Authorization header) so an
|
|
14
|
+
* operator-added extra field — e.g. `connectionTimeoutMs` — does not force a
|
|
15
|
+
* rewrite-and-reload on every activation.
|
|
16
|
+
*/
|
|
17
|
+
function entryMatches(existing, desired) {
|
|
18
|
+
if (!existing || typeof existing !== "object")
|
|
19
|
+
return false;
|
|
20
|
+
const e = existing;
|
|
21
|
+
return (e.url === desired.url &&
|
|
22
|
+
e.transport === desired.transport &&
|
|
23
|
+
e.headers?.Authorization === desired.headers.Authorization);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Ensure the Chorus MCP server is registered with OpenClaw so the agent gains
|
|
27
|
+
* the native `chorus__*` tools.
|
|
28
|
+
*
|
|
29
|
+
* Writes (idempotently) an `mcp.servers.chorus` entry into the OpenClaw config
|
|
30
|
+
* via `api.runtime.config.mutateConfigFile` — a remote streamable-http MCP
|
|
31
|
+
* server with Bearer auth. OpenClaw then connects to the remote Chorus MCP
|
|
32
|
+
* server on (re)load and auto-exposes its tools under the `chorus__` prefix; we
|
|
33
|
+
* never re-declare those tools with `api.registerTool`.
|
|
34
|
+
*
|
|
35
|
+
* Behavior:
|
|
36
|
+
* - Missing `chorusUrl`/`apiKey`: do NOT write; warn naming the missing field(s).
|
|
37
|
+
* - Existing entry already equals the desired entry: return without writing
|
|
38
|
+
* (idempotent — no config reload triggered).
|
|
39
|
+
* - Otherwise: `mutateConfigFile({ afterWrite: { mode: "auto" }, mutate })`,
|
|
40
|
+
* mutating the draft in place (OpenClaw's `mutate` callback receives a
|
|
41
|
+
* structuredClone draft and persists the in-place mutation).
|
|
42
|
+
* - Any rejection from `mutateConfigFile` (or the runtime API being absent) is
|
|
43
|
+
* logged at error level and swallowed — registration failure must never crash
|
|
44
|
+
* the gateway, so the SSE service and `/chorus` command still register.
|
|
45
|
+
*/
|
|
46
|
+
export async function ensureChorusMcpServer(api, cfg) {
|
|
47
|
+
const logger = api.logger;
|
|
48
|
+
// 1. Required-config guard: do not write a half-formed entry.
|
|
49
|
+
const missing = [];
|
|
50
|
+
if (!cfg.chorusUrl)
|
|
51
|
+
missing.push("chorusUrl");
|
|
52
|
+
if (!cfg.apiKey)
|
|
53
|
+
missing.push("apiKey");
|
|
54
|
+
if (missing.length > 0) {
|
|
55
|
+
logger.warn(`[Chorus] Skipping MCP server registration — missing required config: ${missing.join(", ")}`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const desired = buildDesiredEntry(cfg.chorusUrl, cfg.apiKey);
|
|
59
|
+
try {
|
|
60
|
+
// `api.runtime` is permissively typed via the SDK shim; narrow to the
|
|
61
|
+
// config surface we use (current() + mutateConfigFile). At runtime the host
|
|
62
|
+
// provides the real `PluginRuntimeCore.config` API
|
|
63
|
+
// (../openclaw/src/plugins/runtime/types-core.ts:145).
|
|
64
|
+
const runtimeConfig = api.runtime?.config;
|
|
65
|
+
if (!runtimeConfig?.mutateConfigFile) {
|
|
66
|
+
logger.error("[Chorus] MCP server registration failed — runtime.config.mutateConfigFile is unavailable on this host");
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
// 2. Idempotency: skip the write (and the reload it triggers) when the
|
|
70
|
+
// existing entry already matches.
|
|
71
|
+
const existing = runtimeConfig.current?.()?.mcp?.servers?.chorus;
|
|
72
|
+
if (entryMatches(existing, desired)) {
|
|
73
|
+
logger.info("[Chorus] MCP server entry already up to date — no config change");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
// 3. Write the entry, mutating the draft in place.
|
|
77
|
+
await runtimeConfig.mutateConfigFile({
|
|
78
|
+
afterWrite: { mode: "auto" },
|
|
79
|
+
mutate: (draft) => {
|
|
80
|
+
const mcp = (draft.mcp ??= {});
|
|
81
|
+
const servers = (mcp.servers ??= {});
|
|
82
|
+
servers.chorus = desired;
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
logger.info(`[Chorus] Registered MCP server entry — ${desired.url}`);
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
// 4. Never crash the gateway: log at error level and continue so the SSE
|
|
89
|
+
// service and /chorus command still register.
|
|
90
|
+
logger.error(`[Chorus] MCP server registration failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=mcp-registration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-registration.js","sourceRoot":"","sources":["../src/mcp-registration.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export type SseListenerStatus = "connected" | "disconnected" | "reconnecting";
|
|
2
|
+
export interface SseNotificationEvent {
|
|
3
|
+
type: string;
|
|
4
|
+
notificationUuid?: string;
|
|
5
|
+
notificationType?: string;
|
|
6
|
+
unreadCount?: number;
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
}
|
|
9
|
+
export interface ChorusSseListenerOptions {
|
|
10
|
+
chorusUrl: string;
|
|
11
|
+
apiKey: string;
|
|
12
|
+
onEvent: (event: SseNotificationEvent) => void;
|
|
13
|
+
onReconnect: () => Promise<void>;
|
|
14
|
+
logger: {
|
|
15
|
+
info: (msg: string) => void;
|
|
16
|
+
warn: (msg: string) => void;
|
|
17
|
+
error: (msg: string) => void;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export declare class ChorusSseListener {
|
|
21
|
+
private readonly opts;
|
|
22
|
+
private _status;
|
|
23
|
+
private abortController;
|
|
24
|
+
private reconnectTimer;
|
|
25
|
+
private reconnectDelay;
|
|
26
|
+
constructor(opts: ChorusSseListenerOptions);
|
|
27
|
+
get status(): SseListenerStatus;
|
|
28
|
+
/** Start the SSE connection. Resolves once the first bytes arrive (or rejects on immediate failure). */
|
|
29
|
+
connect(): Promise<void>;
|
|
30
|
+
/** Gracefully close the SSE connection. */
|
|
31
|
+
disconnect(): void;
|
|
32
|
+
private consumeStream;
|
|
33
|
+
private processMessage;
|
|
34
|
+
private scheduleReconnect;
|
|
35
|
+
private clearReconnectTimer;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=sse-listener.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse-listener.d.ts","sourceRoot":"","sources":["../src/sse-listener.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,cAAc,GAAG,cAAc,CAAC;AAE9E,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC/C,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,MAAM,EAAE;QAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAAC,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAAC,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CAAC;CACpG;AAKD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA2B;IAChD,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,eAAe,CAAgC;IACvD,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,cAAc,CAAoB;gBAE9B,IAAI,EAAE,wBAAwB;IAI1C,IAAI,MAAM,IAAI,iBAAiB,CAE9B;IAED,wGAAwG;IAClG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAuD9B,2CAA2C;IAC3C,UAAU,IAAI,IAAI;YAcJ,aAAa;IAsC3B,OAAO,CAAC,cAAc;IAkBtB,OAAO,CAAC,iBAAiB;IAczB,OAAO,CAAC,mBAAmB;CAM5B"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
const INITIAL_DELAY_MS = 1_000;
|
|
2
|
+
const MAX_DELAY_MS = 30_000;
|
|
3
|
+
export class ChorusSseListener {
|
|
4
|
+
opts;
|
|
5
|
+
_status = "disconnected";
|
|
6
|
+
abortController = null;
|
|
7
|
+
reconnectTimer = null;
|
|
8
|
+
reconnectDelay = INITIAL_DELAY_MS;
|
|
9
|
+
constructor(opts) {
|
|
10
|
+
this.opts = opts;
|
|
11
|
+
}
|
|
12
|
+
get status() {
|
|
13
|
+
return this._status;
|
|
14
|
+
}
|
|
15
|
+
/** Start the SSE connection. Resolves once the first bytes arrive (or rejects on immediate failure). */
|
|
16
|
+
async connect() {
|
|
17
|
+
this.clearReconnectTimer();
|
|
18
|
+
const abortController = new AbortController();
|
|
19
|
+
this.abortController = abortController;
|
|
20
|
+
const url = `${this.opts.chorusUrl.replace(/\/$/, "")}/api/events/notifications`;
|
|
21
|
+
let response;
|
|
22
|
+
try {
|
|
23
|
+
response = await fetch(url, {
|
|
24
|
+
headers: {
|
|
25
|
+
Authorization: `Bearer ${this.opts.apiKey}`,
|
|
26
|
+
Accept: "text/event-stream",
|
|
27
|
+
},
|
|
28
|
+
signal: abortController.signal,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
if (abortController.signal.aborted)
|
|
33
|
+
return; // intentional disconnect
|
|
34
|
+
this.opts.logger.error(`SSE connection failed: ${err}`);
|
|
35
|
+
this.scheduleReconnect();
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
this.opts.logger.error(`SSE endpoint returned ${response.status}`);
|
|
40
|
+
this.scheduleReconnect();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (!response.body) {
|
|
44
|
+
this.opts.logger.error("SSE response has no body");
|
|
45
|
+
this.scheduleReconnect();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
// Connection succeeded — reset backoff
|
|
49
|
+
const isReconnect = this._status === "reconnecting";
|
|
50
|
+
this._status = "connected";
|
|
51
|
+
this.reconnectDelay = INITIAL_DELAY_MS;
|
|
52
|
+
this.opts.logger.info("[Chorus] SSE connection established");
|
|
53
|
+
if (isReconnect) {
|
|
54
|
+
// Fire onReconnect callback so the caller can back-fill missed notifications
|
|
55
|
+
try {
|
|
56
|
+
await this.opts.onReconnect();
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
this.opts.logger.warn(`onReconnect callback error: ${err}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// Read the stream
|
|
63
|
+
this.consumeStream(response.body, abortController.signal);
|
|
64
|
+
}
|
|
65
|
+
/** Gracefully close the SSE connection. */
|
|
66
|
+
disconnect() {
|
|
67
|
+
this.clearReconnectTimer();
|
|
68
|
+
if (this.abortController) {
|
|
69
|
+
this.abortController.abort();
|
|
70
|
+
this.abortController = null;
|
|
71
|
+
}
|
|
72
|
+
this._status = "disconnected";
|
|
73
|
+
this.opts.logger.info("SSE connection closed");
|
|
74
|
+
}
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
// Internal
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
async consumeStream(body, signal) {
|
|
79
|
+
const decoder = new TextDecoder();
|
|
80
|
+
const reader = body.getReader();
|
|
81
|
+
let buffer = "";
|
|
82
|
+
try {
|
|
83
|
+
while (true) {
|
|
84
|
+
const { done, value } = await reader.read();
|
|
85
|
+
if (done || signal.aborted)
|
|
86
|
+
break;
|
|
87
|
+
buffer += decoder.decode(value, { stream: true });
|
|
88
|
+
// SSE messages are delimited by double newlines
|
|
89
|
+
let boundary;
|
|
90
|
+
while ((boundary = buffer.indexOf("\n\n")) !== -1) {
|
|
91
|
+
const raw = buffer.slice(0, boundary);
|
|
92
|
+
buffer = buffer.slice(boundary + 2);
|
|
93
|
+
this.processMessage(raw);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
if (signal.aborted)
|
|
99
|
+
return; // intentional disconnect
|
|
100
|
+
this.opts.logger.warn(`SSE stream error: ${err}`);
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
try {
|
|
104
|
+
reader.releaseLock();
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// already released
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Stream ended unexpectedly — reconnect unless we were intentionally disconnected
|
|
111
|
+
if (!signal.aborted) {
|
|
112
|
+
this.opts.logger.warn("SSE stream ended, scheduling reconnect");
|
|
113
|
+
this.scheduleReconnect();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
processMessage(raw) {
|
|
117
|
+
for (const line of raw.split("\n")) {
|
|
118
|
+
// Comment lines (heartbeats) — ignore
|
|
119
|
+
if (line.startsWith(":"))
|
|
120
|
+
continue;
|
|
121
|
+
// Data lines
|
|
122
|
+
if (line.startsWith("data: ")) {
|
|
123
|
+
const jsonStr = line.slice(6);
|
|
124
|
+
try {
|
|
125
|
+
const event = JSON.parse(jsonStr);
|
|
126
|
+
this.opts.onEvent(event);
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
this.opts.logger.warn(`SSE JSON parse error: ${err} — raw: ${jsonStr}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
scheduleReconnect() {
|
|
135
|
+
this.clearReconnectTimer();
|
|
136
|
+
this._status = "reconnecting";
|
|
137
|
+
this.opts.logger.info(`SSE reconnecting in ${this.reconnectDelay}ms`);
|
|
138
|
+
this.reconnectTimer = setTimeout(() => {
|
|
139
|
+
this.reconnectTimer = null;
|
|
140
|
+
this.connect();
|
|
141
|
+
}, this.reconnectDelay);
|
|
142
|
+
// Exponential backoff: 1s → 2s → 4s → 8s → 16s → 30s
|
|
143
|
+
this.reconnectDelay = Math.min(this.reconnectDelay * 2, MAX_DELAY_MS);
|
|
144
|
+
}
|
|
145
|
+
clearReconnectTimer() {
|
|
146
|
+
if (this.reconnectTimer !== null) {
|
|
147
|
+
clearTimeout(this.reconnectTimer);
|
|
148
|
+
this.reconnectTimer = null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=sse-listener.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse-listener.js","sourceRoot":"","sources":["../src/sse-listener.ts"],"names":[],"mappings":"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"}
|
package/dist/wake.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
|
|
2
|
+
/**
|
|
3
|
+
* Resolve the main agent session key from the host config.
|
|
4
|
+
*
|
|
5
|
+
* This faithfully replicates OpenClaw's `resolveMainSessionKey(cfg)`
|
|
6
|
+
* (`../openclaw/src/config/sessions/main-session.ts:14`): a background plugin
|
|
7
|
+
* has no inbound message route to derive a session key from, so the wake must
|
|
8
|
+
* target the main agent's session. We cannot import the OpenClaw internal here
|
|
9
|
+
* (the resolvable `openclaw` peer is an older build), so we re-derive the key
|
|
10
|
+
* from `api.config` (the live `OpenClawConfig` snapshot) using the same rules:
|
|
11
|
+
*
|
|
12
|
+
* - `session.scope === "global"` → the literal `"global"` queue
|
|
13
|
+
* - otherwise → `agent:<defaultAgentId>:<mainKey>`, where defaultAgentId is
|
|
14
|
+
* the agent flagged `default`, else the first listed agent, else "main".
|
|
15
|
+
*
|
|
16
|
+
* Returns `null` when no session key can be resolved. The design's no-session
|
|
17
|
+
* fallback (graceful drop) then applies — we never fabricate a session.
|
|
18
|
+
*/
|
|
19
|
+
export declare function resolveSessionKey(api: OpenClawPluginApi): string | null;
|
|
20
|
+
/**
|
|
21
|
+
* Resolve the default agent id (matches OpenClaw's `resolveDefaultAgentId`):
|
|
22
|
+
* the agent flagged `default`, else the first listed agent, else "main".
|
|
23
|
+
* runEmbeddedAgent and the session/workspace resolvers are all agent-scoped.
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolveAgentId(api: OpenClawPluginApi): string;
|
|
26
|
+
/**
|
|
27
|
+
* Resolve the configured default model into `{ provider, model }` so we can pass
|
|
28
|
+
* them explicitly to runEmbeddedAgent. WHY THIS IS REQUIRED: runEmbeddedAgent
|
|
29
|
+
* does NOT auto-resolve the model from config when `provider`/`model` are
|
|
30
|
+
* omitted — it falls back to the built-in `DEFAULT_MODEL` ("gpt-5.5",
|
|
31
|
+
* `../openclaw/src/agents/defaults.ts:4`), which fails with "Unknown model"
|
|
32
|
+
* unless that happens to be configured. The host's own resolver
|
|
33
|
+
* (`resolveConfiguredModelRef`, `../openclaw/src/agents/model-selection-shared.ts`)
|
|
34
|
+
* is NOT exposed to plugins, so we mirror its primary-ref read: `agents.defaults.model`
|
|
35
|
+
* is either a bare `"provider/model"` string or `{ primary: "provider/model" }`
|
|
36
|
+
* (`resolvePrimaryStringValue`), split on the first "/".
|
|
37
|
+
*
|
|
38
|
+
* Returns null when no default model is configured (then we omit the overrides
|
|
39
|
+
* and let the host fall back — same as before, but at least we tried).
|
|
40
|
+
*/
|
|
41
|
+
export declare function resolveModelRef(api: OpenClawPluginApi): {
|
|
42
|
+
provider: string;
|
|
43
|
+
model: string;
|
|
44
|
+
} | null;
|
|
45
|
+
/**
|
|
46
|
+
* Build a `wake(message, contextKey)` callback bound to the host runtime.
|
|
47
|
+
*
|
|
48
|
+
* Each wake resolves the main agent's session + workspace and runs an agent
|
|
49
|
+
* turn IN-PROCESS with `message` as the prompt (via runEmbeddedAgent). The turn
|
|
50
|
+
* has the agent's normal tool set (including the `chorus__*` MCP tools), so the
|
|
51
|
+
* agent can read the entity, the comment thread, and post back to Chorus.
|
|
52
|
+
*
|
|
53
|
+
* Failure modes (never throw — the SSE service must stay alive):
|
|
54
|
+
* - no resolvable session key / agent runtime unavailable → log + drop;
|
|
55
|
+
* - no existing session entry → run with a fresh sessionId (the runner
|
|
56
|
+
* creates the transcript); the agent starts a new conversation;
|
|
57
|
+
* - runEmbeddedAgent rejects (e.g. a turn is already in flight) → log + drop
|
|
58
|
+
* (the next SSE event re-triggers).
|
|
59
|
+
*
|
|
60
|
+
* Runs are fire-and-forget (the SSE dispatch path is sync); we log completion.
|
|
61
|
+
*/
|
|
62
|
+
export declare function createWake(api: OpenClawPluginApi, logger: {
|
|
63
|
+
info: (msg: string) => void;
|
|
64
|
+
warn: (msg: string) => void;
|
|
65
|
+
error: (msg: string) => void;
|
|
66
|
+
}): (message: string, contextKey: string) => void;
|
|
67
|
+
//# sourceMappingURL=wake.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wake.d.ts","sourceRoot":"","sources":["../src/wake.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAwD1E;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,iBAAiB,GAAG,MAAM,GAAG,IAAI,CAoBvE;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,iBAAiB,GAAG,MAAM,CAO7D;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,iBAAiB,GACrB;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAe5C;AAoDD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,UAAU,CACxB,GAAG,EAAE,iBAAiB,EACtB,MAAM,EAAE;IAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAAC,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAAC,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,GACjG,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,IAAI,CAgG/C"}
|