@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.
Files changed (58) hide show
  1. package/README.md +218 -218
  2. package/dist/commands.d.ts +5 -0
  3. package/dist/commands.d.ts.map +1 -0
  4. package/dist/commands.js +147 -0
  5. package/dist/commands.js.map +1 -0
  6. package/dist/config.d.ts +38 -0
  7. package/dist/config.d.ts.map +1 -0
  8. package/dist/config.js +57 -0
  9. package/dist/config.js.map +1 -0
  10. package/dist/event-router.d.ts +55 -0
  11. package/dist/event-router.d.ts.map +1 -0
  12. package/dist/event-router.js +157 -0
  13. package/dist/event-router.js.map +1 -0
  14. package/dist/index.d.ts +3 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +108 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/mcp-client.d.ts +37 -0
  19. package/dist/mcp-client.d.ts.map +1 -0
  20. package/dist/mcp-client.js +137 -0
  21. package/dist/mcp-client.js.map +1 -0
  22. package/dist/mcp-registration.d.ts +25 -0
  23. package/dist/mcp-registration.d.ts.map +1 -0
  24. package/dist/mcp-registration.js +93 -0
  25. package/dist/mcp-registration.js.map +1 -0
  26. package/dist/sse-listener.d.ts +37 -0
  27. package/dist/sse-listener.d.ts.map +1 -0
  28. package/dist/sse-listener.js +152 -0
  29. package/dist/sse-listener.js.map +1 -0
  30. package/dist/wake.d.ts +67 -0
  31. package/dist/wake.d.ts.map +1 -0
  32. package/dist/wake.js +234 -0
  33. package/dist/wake.js.map +1 -0
  34. package/openclaw.plugin.json +14 -12
  35. package/package.json +24 -5
  36. package/skills/brainstorm/SKILL.md +163 -0
  37. package/skills/chorus/SKILL.md +413 -0
  38. package/skills/develop/SKILL.md +434 -0
  39. package/skills/idea/SKILL.md +293 -0
  40. package/skills/openspec-aware/SKILL.md +425 -0
  41. package/skills/proposal/SKILL.md +397 -0
  42. package/skills/proposal-reviewer/SKILL.md +117 -0
  43. package/skills/quick-dev/SKILL.md +198 -0
  44. package/skills/review/SKILL.md +354 -0
  45. package/skills/task-reviewer/SKILL.md +113 -0
  46. package/skills/yolo/SKILL.md +498 -0
  47. package/src/commands.ts +147 -57
  48. package/src/config.ts +23 -10
  49. package/src/event-router.ts +46 -54
  50. package/src/index.ts +56 -83
  51. package/src/mcp-client.ts +17 -0
  52. package/src/mcp-registration.ts +142 -0
  53. package/src/openclaw-sdk.d.ts +95 -0
  54. package/src/wake.ts +310 -0
  55. package/src/tools/admin-tools.ts +0 -117
  56. package/src/tools/common-tools.ts +0 -546
  57. package/src/tools/dev-tools.ts +0 -97
  58. 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":"AAkBA,MAAM,gBAAgB,GAAG,KAAK,CAAC;AAC/B,MAAM,YAAY,GAAG,MAAM,CAAC;AAE5B,MAAM,OAAO,iBAAiB;IACX,IAAI,CAA2B;IACxC,OAAO,GAAsB,cAAc,CAAC;IAC5C,eAAe,GAA2B,IAAI,CAAC;IAC/C,cAAc,GAAyC,IAAI,CAAC;IAC5D,cAAc,GAAG,gBAAgB,CAAC;IAE1C,YAAY,IAA8B;QACxC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;IAED,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,wGAAwG;IACxG,KAAK,CAAC,OAAO;QACX,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAE3B,MAAM,eAAe,GAAG,IAAI,eAAe,EAAE,CAAC;QAC9C,IAAI,CAAC,eAAe,GAAG,eAAe,CAAC;QAEvC,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,2BAA2B,CAAC;QAEjF,IAAI,QAAkB,CAAC;QACvB,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;gBAC1B,OAAO,EAAE;oBACP,aAAa,EAAE,UAAU,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;oBAC3C,MAAM,EAAE,mBAAmB;iBAC5B;gBACD,MAAM,EAAE,eAAe,CAAC,MAAM;aAC/B,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,eAAe,CAAC,MAAM,CAAC,OAAO;gBAAE,OAAO,CAAC,yBAAyB;YACrE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,0BAA0B,GAAG,EAAE,CAAC,CAAC;YACxD,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACzB,OAAO;QACT,CAAC;QAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,yBAAyB,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;YACnE,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACzB,OAAO;QACT,CAAC;QAED,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;YACnB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;YACnD,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACzB,OAAO;QACT,CAAC;QAED,uCAAuC;QACvC,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,KAAK,cAAc,CAAC;QACpD,IAAI,CAAC,OAAO,GAAG,WAAW,CAAC;QAC3B,IAAI,CAAC,cAAc,GAAG,gBAAgB,CAAC;QACvC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,qCAAqC,CAAC,CAAC;QAE7D,IAAI,WAAW,EAAE,CAAC;YAChB,6EAA6E;YAC7E,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YAChC,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,+BAA+B,GAAG,EAAE,CAAC,CAAC;YAC9D,CAAC;QACH,CAAC;QAED,kBAAkB;QAClB,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC;IAC5D,CAAC;IAED,2CAA2C;IAC3C,UAAU;QACR,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC3B,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YACzB,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;YAC7B,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;QAC9B,CAAC;QACD,IAAI,CAAC,OAAO,GAAG,cAAc,CAAC;QAC9B,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;IACjD,CAAC;IAED,8EAA8E;IAC9E,WAAW;IACX,8EAA8E;IAEtE,KAAK,CAAC,aAAa,CAAC,IAAgC,EAAE,MAAmB;QAC/E,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAChC,IAAI,MAAM,GAAG,EAAE,CAAC;QAEhB,IAAI,CAAC;YACH,OAAO,IAAI,EAAE,CAAC;gBACZ,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;gBAC5C,IAAI,IAAI,IAAI,MAAM,CAAC,OAAO;oBAAE,MAAM;gBAElC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;gBAElD,gDAAgD;gBAChD,IAAI,QAAgB,CAAC;gBACrB,OAAO,CAAC,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;oBAClD,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;oBACtC,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC;oBACpC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;gBAC3B,CAAC;YACH,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,MAAM,CAAC,OAAO;gBAAE,OAAO,CAAC,yBAAyB;YACrD,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,qBAAqB,GAAG,EAAE,CAAC,CAAC;QACpD,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC;gBACH,MAAM,CAAC,WAAW,EAAE,CAAC;YACvB,CAAC;YAAC,MAAM,CAAC;gBACP,mBAAmB;YACrB,CAAC;QACH,CAAC;QAED,kFAAkF;QAClF,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,wCAAwC,CAAC,CAAC;YAChE,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAC3B,CAAC;IACH,CAAC;IAEO,cAAc,CAAC,GAAW;QAChC,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACnC,sCAAsC;YACtC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;gBAAE,SAAS;YAEnC,aAAa;YACb,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC9B,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBAC9B,IAAI,CAAC;oBACH,MAAM,KAAK,GAAyB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;oBACxD,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;gBAC3B,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,yBAAyB,GAAG,WAAW,OAAO,EAAE,CAAC,CAAC;gBAC1E,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAEO,iBAAiB;QACvB,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC3B,IAAI,CAAC,OAAO,GAAG,cAAc,CAAC;QAE9B,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,uBAAuB,IAAI,CAAC,cAAc,IAAI,CAAC,CAAC;QACtE,IAAI,CAAC,cAAc,GAAG,UAAU,CAAC,GAAG,EAAE;YACpC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;YAC3B,IAAI,CAAC,OAAO,EAAE,CAAC;QACjB,CAAC,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;QAExB,qDAAqD;QACrD,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,cAAc,GAAG,CAAC,EAAE,YAAY,CAAC,CAAC;IACxE,CAAC;IAEO,mBAAmB;QACzB,IAAI,IAAI,CAAC,cAAc,KAAK,IAAI,EAAE,CAAC;YACjC,YAAY,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;YAClC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAC7B,CAAC;IACH,CAAC;CACF"}
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"}