@chorus-aidlc/chorus-openclaw-plugin 0.10.0 → 0.11.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/dist/connection-state.d.ts +35 -0
- package/dist/connection-state.d.ts.map +1 -0
- package/dist/connection-state.js +52 -0
- package/dist/connection-state.js.map +1 -0
- package/dist/control-handler.d.ts +73 -0
- package/dist/control-handler.d.ts.map +1 -0
- package/dist/control-handler.js +135 -0
- package/dist/control-handler.js.map +1 -0
- package/dist/daemon-client.d.ts +203 -0
- package/dist/daemon-client.d.ts.map +1 -0
- package/dist/daemon-client.js +469 -0
- package/dist/daemon-client.js.map +1 -0
- package/dist/daemon-rest-client.d.ts +86 -0
- package/dist/daemon-rest-client.d.ts.map +1 -0
- package/dist/daemon-rest-client.js +196 -0
- package/dist/daemon-rest-client.js.map +1 -0
- package/dist/event-router.d.ts +31 -6
- package/dist/event-router.d.ts.map +1 -1
- package/dist/event-router.js +58 -27
- package/dist/event-router.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +106 -7
- package/dist/index.js.map +1 -1
- package/dist/lineage.d.ts +44 -0
- package/dist/lineage.d.ts.map +1 -0
- package/dist/lineage.js +116 -0
- package/dist/lineage.js.map +1 -0
- package/dist/mcp-registration.d.ts.map +1 -1
- package/dist/mcp-registration.js +5 -4
- package/dist/mcp-registration.js.map +1 -1
- package/dist/sse-listener.d.ts +34 -0
- package/dist/sse-listener.d.ts.map +1 -1
- package/dist/sse-listener.js +78 -4
- package/dist/sse-listener.js.map +1 -1
- package/dist/wake.d.ts +20 -0
- package/dist/wake.d.ts.map +1 -1
- package/dist/wake.js +56 -0
- package/dist/wake.js.map +1 -1
- package/package.json +1 -1
- package/skills/brainstorm/SKILL.md +1 -1
- package/skills/chorus/SKILL.md +36 -5
- package/skills/develop/SKILL.md +1 -1
- package/skills/idea/SKILL.md +1 -1
- package/skills/openspec-aware/SKILL.md +1 -1
- package/skills/proposal/SKILL.md +1 -1
- package/skills/proposal-reviewer/SKILL.md +1 -1
- package/skills/quick-dev/SKILL.md +1 -1
- package/skills/review/SKILL.md +1 -1
- package/skills/task-reviewer/SKILL.md +1 -1
- package/skills/yolo/SKILL.md +1 -1
- package/src/connection-state.ts +66 -0
- package/src/control-handler.ts +219 -0
- package/src/daemon-client.ts +622 -0
- package/src/daemon-rest-client.ts +312 -0
- package/src/event-router.ts +103 -33
- package/src/index.ts +113 -8
- package/src/lineage.ts +157 -0
- package/src/mcp-registration.ts +6 -19
- package/src/openclaw-sdk.d.ts +232 -1
- package/src/sse-listener.ts +117 -5
- package/src/wake.ts +69 -26
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// packages/openclaw-plugin/src/daemon-rest-client.ts
|
|
2
|
+
// TypeScript mirror of the shared, host-agnostic pure-REST client for the Chorus
|
|
3
|
+
// daemon → server reporting surface (`/api/daemon/*`).
|
|
4
|
+
//
|
|
5
|
+
// WHY A MIRROR (not an import): the single-source-of-truth implementation is
|
|
6
|
+
// `cli/daemon-rest-client.mjs`, consumed verbatim by the chorus CLI daemon. The
|
|
7
|
+
// OpenClaw plugin, however, is a SEPARATELY-PUBLISHED npm package
|
|
8
|
+
// (`@chorus-aidlc/chorus-openclaw-plugin`, `files: ["src", "dist", ...]`) whose
|
|
9
|
+
// TS build has `rootDir: "src"`. Importing a file under `cli/` (outside both the
|
|
10
|
+
// package boundary AND rootDir) is rejected by `tsc` and would not be present in
|
|
11
|
+
// the published tarball. So we mirror the EXACT same factory + payload shapes here
|
|
12
|
+
// — this is NOT a fork of the wire contract: every payload below is byte-for-byte
|
|
13
|
+
// the shape `cli/daemon-rest-client.mjs` sends (and the server already accepts).
|
|
14
|
+
// The two files are kept in lock-step by the spec's "single source of truth for
|
|
15
|
+
// the payload shapes" requirement; a drift would be caught by T5 (live e2e).
|
|
16
|
+
//
|
|
17
|
+
// The five operations and their EXACT server payload shapes (verified against
|
|
18
|
+
// cli/daemon-rest-client.mjs + src/app/api/daemon/*/route.ts — server unchanged):
|
|
19
|
+
// turnAdvance → POST /api/daemon/turn-advance
|
|
20
|
+
// { connectionUuid, sessionId, status, entityType?, entityUuid? }
|
|
21
|
+
// transcript → POST /api/daemon/transcript
|
|
22
|
+
// { sessionId, messages: [{ role, text }] }
|
|
23
|
+
// executionState → POST /api/daemon/execution-state
|
|
24
|
+
// { connectionUuid, executions: [{ entityType, entityUuid,
|
|
25
|
+
// rootIdeaUuid|null, status,
|
|
26
|
+
// startedAt|null }] }
|
|
27
|
+
// reportInterrupt → POST /api/daemon/report-interrupt
|
|
28
|
+
// { connectionUuid, entityType, entityUuid, reason }
|
|
29
|
+
// readPendingTurns → GET /api/daemon/pending-turns?connectionUuid=…
|
|
30
|
+
// → { turns: [{ turnUuid, sessionId, directIdeaUuid, trigger,
|
|
31
|
+
// promptText }] }
|
|
32
|
+
//
|
|
33
|
+
// HARD CONSTRAINTS (identical to the CLI client):
|
|
34
|
+
// • ZERO daemon-host coupling — no child_process, no OpenClaw SDK import. Its only
|
|
35
|
+
// outbound effect is HTTP via the injected `fetchImpl` (global fetch on Node 18+).
|
|
36
|
+
// Adds NO new npm dependency (CLAUDE.md pitfall #9).
|
|
37
|
+
// • Bearer-only auth: every request carries `Authorization: Bearer <apiKey>`.
|
|
38
|
+
// • NO SILENT ERRORS (project policy): a network error, a non-2xx response, or a
|
|
39
|
+
// bad/empty body is LOGGED WITH ITS CAUSE and SURFACED via a structured result —
|
|
40
|
+
// never swallowed into a silent success.
|
|
41
|
+
// • A failed report NEVER rejects: every method RESOLVES with `{ ok: false, ... }`
|
|
42
|
+
// so a fire-and-forget caller can `await` it safely.
|
|
43
|
+
const NOOP_LOGGER = { info() { }, warn() { }, error() { } };
|
|
44
|
+
/**
|
|
45
|
+
* Build the shared daemon REST client. Inputs are entirely host-agnostic, which is
|
|
46
|
+
* exactly why the same surface serves both daemon hosts.
|
|
47
|
+
*/
|
|
48
|
+
export function createDaemonRestClient(opts) {
|
|
49
|
+
const url = opts.url.replace(/\/$/, "");
|
|
50
|
+
const apiKey = opts.apiKey;
|
|
51
|
+
const getConnectionUuid = opts.getConnectionUuid ?? (() => null);
|
|
52
|
+
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
53
|
+
const logger = opts.logger ?? NOOP_LOGGER;
|
|
54
|
+
const jsonHeaders = {
|
|
55
|
+
Authorization: `Bearer ${apiKey}`,
|
|
56
|
+
"Content-Type": "application/json",
|
|
57
|
+
Accept: "application/json",
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Issue one daemon report. Owns the transport + the no-silent-errors contract
|
|
61
|
+
* IDENTICAL across all four POST endpoints; only the `op` label and the path
|
|
62
|
+
* differ. Never throws — returns a structured {@link DaemonRestResult}.
|
|
63
|
+
*/
|
|
64
|
+
async function post(op, path, body, successLog, context = "") {
|
|
65
|
+
let response;
|
|
66
|
+
try {
|
|
67
|
+
response = await fetchImpl(`${url}${path}`, {
|
|
68
|
+
method: "POST",
|
|
69
|
+
headers: jsonHeaders,
|
|
70
|
+
body: JSON.stringify(body),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
// Network-level failure (DNS, connection refused, abort, …). Surface WITH cause.
|
|
75
|
+
const error = `${op} request failed${context}: ${err}`;
|
|
76
|
+
logger.warn(`[Chorus] ${error}`);
|
|
77
|
+
return { ok: false, status: null, error };
|
|
78
|
+
}
|
|
79
|
+
if (!response.ok) {
|
|
80
|
+
// Non-2xx. Surface WITH the status so a 4xx/5xx is debuggable.
|
|
81
|
+
const error = `${op} returned ${response.status}${context}`;
|
|
82
|
+
logger.warn(`[Chorus] ${error}`);
|
|
83
|
+
return { ok: false, status: response.status, error };
|
|
84
|
+
}
|
|
85
|
+
if (successLog)
|
|
86
|
+
logger.info(`[Chorus] ${successLog}`);
|
|
87
|
+
return { ok: true, status: response.status };
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
/**
|
|
91
|
+
* POST /api/daemon/turn-advance — advance a wake's DaemonSessionTurn lifecycle.
|
|
92
|
+
* The server resolves the turn by the session BUSINESS KEY (`sessionId`); the
|
|
93
|
+
* optional `entityType`/`entityUuid` stamp the weak executionUuid link. Requires
|
|
94
|
+
* the connectionUuid.
|
|
95
|
+
*/
|
|
96
|
+
async turnAdvance({ sessionId, status, entityType, entityUuid }) {
|
|
97
|
+
const connectionUuid = getConnectionUuid();
|
|
98
|
+
if (!connectionUuid) {
|
|
99
|
+
const error = `cannot advance turn for session ${sessionId} → ${status} — no connection uuid yet`;
|
|
100
|
+
logger.warn(`[Chorus] ${error}`);
|
|
101
|
+
return { ok: false, status: null, error, skipped: true };
|
|
102
|
+
}
|
|
103
|
+
const body = {
|
|
104
|
+
connectionUuid,
|
|
105
|
+
sessionId,
|
|
106
|
+
status,
|
|
107
|
+
// Only sent when BOTH are present, so the server never gets a partial linkage.
|
|
108
|
+
...(entityType && entityUuid ? { entityType, entityUuid } : {}),
|
|
109
|
+
};
|
|
110
|
+
return post("turn-advance", "/api/daemon/turn-advance", body, `advanced turn for session ${sessionId} → ${status}`);
|
|
111
|
+
},
|
|
112
|
+
/**
|
|
113
|
+
* POST /api/daemon/transcript — append finalized user/assistant text to the
|
|
114
|
+
* current turn, targeted by the session BUSINESS KEY. The caller owns the content
|
|
115
|
+
* filter (only `{ role, text }`) and any batching. No connectionUuid needed.
|
|
116
|
+
*/
|
|
117
|
+
async transcript({ sessionId, messages }) {
|
|
118
|
+
return post("transcript upload", "/api/daemon/transcript", { sessionId, messages }, `transcript uploaded (${messages.length} msg) for session ${sessionId}`);
|
|
119
|
+
},
|
|
120
|
+
/**
|
|
121
|
+
* POST /api/daemon/execution-state — publish the connection's running/queued
|
|
122
|
+
* execution snapshot (caller supplies the already-built `executions` array).
|
|
123
|
+
* Requires the connectionUuid; a null uuid is a normal early state (silent skip).
|
|
124
|
+
*/
|
|
125
|
+
async executionState({ executions }) {
|
|
126
|
+
const connectionUuid = getConnectionUuid();
|
|
127
|
+
if (!connectionUuid) {
|
|
128
|
+
return { ok: false, status: null, skipped: true };
|
|
129
|
+
}
|
|
130
|
+
return post("execution-state upload", "/api/daemon/execution-state", { connectionUuid, executions }, `execution-state uploaded (${executions.length} active)`);
|
|
131
|
+
},
|
|
132
|
+
/**
|
|
133
|
+
* POST /api/daemon/report-interrupt — record a wake's `interrupted` outcome
|
|
134
|
+
* (reason = "user" | "crash") on the execution row keyed by connection + entity.
|
|
135
|
+
*/
|
|
136
|
+
async reportInterrupt({ entityType, entityUuid, reason }) {
|
|
137
|
+
const connectionUuid = getConnectionUuid();
|
|
138
|
+
if (!connectionUuid) {
|
|
139
|
+
const error = `cannot report interrupt for ${entityType}:${entityUuid} — no connection uuid yet`;
|
|
140
|
+
logger.warn(`[Chorus] ${error}`);
|
|
141
|
+
return { ok: false, status: null, error, skipped: true };
|
|
142
|
+
}
|
|
143
|
+
return post("report-interrupt", "/api/daemon/report-interrupt", { connectionUuid, entityType, entityUuid, reason }, `reported ${entityType}:${entityUuid} interrupted (reason=${reason})`, ` for ${entityType}:${entityUuid}`);
|
|
144
|
+
},
|
|
145
|
+
/**
|
|
146
|
+
* GET /api/daemon/pending-turns?connectionUuid=… — read this connection's
|
|
147
|
+
* unstarted (pending) turns. Returns the parsed `{ turns: [...] }` data on
|
|
148
|
+
* success; a network error / non-2xx / bad body / missing array is logged with
|
|
149
|
+
* cause and surfaced as a failure result — never a silent empty success.
|
|
150
|
+
*/
|
|
151
|
+
async readPendingTurns() {
|
|
152
|
+
const connectionUuid = getConnectionUuid();
|
|
153
|
+
if (!connectionUuid) {
|
|
154
|
+
return { ok: false, status: null, skipped: true };
|
|
155
|
+
}
|
|
156
|
+
const endpoint = `${url}/api/daemon/pending-turns?connectionUuid=${encodeURIComponent(connectionUuid)}`;
|
|
157
|
+
let response;
|
|
158
|
+
try {
|
|
159
|
+
response = await fetchImpl(endpoint, {
|
|
160
|
+
headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
catch (err) {
|
|
164
|
+
const error = `pending-turns backfill request failed: ${err}`;
|
|
165
|
+
logger.warn(`[Chorus] ${error}`);
|
|
166
|
+
return { ok: false, status: null, error };
|
|
167
|
+
}
|
|
168
|
+
if (!response.ok) {
|
|
169
|
+
const error = `pending-turns backfill returned ${response.status}`;
|
|
170
|
+
logger.warn(`[Chorus] ${error}`);
|
|
171
|
+
return { ok: false, status: response.status, error };
|
|
172
|
+
}
|
|
173
|
+
let parsed;
|
|
174
|
+
try {
|
|
175
|
+
parsed = await response.json();
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
const error = `pending-turns backfill: bad JSON: ${err}`;
|
|
179
|
+
logger.warn(`[Chorus] ${error}`);
|
|
180
|
+
return { ok: false, status: response.status, error };
|
|
181
|
+
}
|
|
182
|
+
// API envelope: { success: true, data: { turns: [...] } }.
|
|
183
|
+
const data = parsed && typeof parsed === "object"
|
|
184
|
+
? parsed.data
|
|
185
|
+
: undefined;
|
|
186
|
+
const turns = data && typeof data === "object" ? data.turns : undefined;
|
|
187
|
+
if (!Array.isArray(turns)) {
|
|
188
|
+
const error = "pending-turns backfill: no turns array in response";
|
|
189
|
+
logger.warn(`[Chorus] ${error}`);
|
|
190
|
+
return { ok: false, status: response.status, error };
|
|
191
|
+
}
|
|
192
|
+
return { ok: true, status: response.status, data: { turns: turns } };
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=daemon-rest-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon-rest-client.js","sourceRoot":"","sources":["../src/daemon-rest-client.ts"],"names":[],"mappings":"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"}
|
package/dist/event-router.d.ts
CHANGED
|
@@ -1,19 +1,43 @@
|
|
|
1
1
|
import type { ChorusMcpClient } from "./mcp-client.js";
|
|
2
2
|
import type { SseNotificationEvent } from "./sse-listener.js";
|
|
3
|
+
import type { LineageResolver } from "./lineage.js";
|
|
4
|
+
/**
|
|
5
|
+
* Per-wake attribution the router resolves and threads to the wake. Carries the
|
|
6
|
+
* reportable resource (`entityType`/`entityUuid`) and the lineage ids (the two-id
|
|
7
|
+
* contract: `directIdeaUuid` = session anchor, `rootIdeaUuid` = snapshot attribution).
|
|
8
|
+
* All optional so a host wired WITHOUT the daemon client (no lineage) still wakes —
|
|
9
|
+
* the daemon-reporting fields are simply absent and the run is a plain wake.
|
|
10
|
+
*/
|
|
11
|
+
export interface WakeAttribution {
|
|
12
|
+
entityType?: string | null;
|
|
13
|
+
entityUuid?: string | null;
|
|
14
|
+
directIdeaUuid?: string | null;
|
|
15
|
+
rootIdeaUuid?: string | null;
|
|
16
|
+
}
|
|
3
17
|
/**
|
|
4
18
|
* Wake callback injected by the entry. Runs an embedded agent turn on the main
|
|
5
|
-
* agent's session with `message` as the prompt (see `wake.ts`
|
|
6
|
-
* which
|
|
19
|
+
* agent's session with `message` as the prompt (see `wake.ts` / daemon-client.ts,
|
|
20
|
+
* which call `api.runtime.agent.runEmbeddedAgent`).
|
|
7
21
|
*
|
|
8
22
|
* `contextKey` identifies the originating Chorus action+entity (e.g.
|
|
9
|
-
* `chorus:mentioned:<uuid>`); it is used for the run id / logging.
|
|
10
|
-
*
|
|
11
|
-
*
|
|
23
|
+
* `chorus:mentioned:<uuid>`); it is used for the run id / logging. `attribution`
|
|
24
|
+
* (when present) lets the daemon client report turn-advance / execution-state /
|
|
25
|
+
* interrupt for the wake and anchor the session on the business key. The wake
|
|
26
|
+
* resolves the main agent session + model and DROPS (logs + returns) when it cannot
|
|
27
|
+
* run — it never throws, so the SSE service stays alive.
|
|
12
28
|
*/
|
|
13
|
-
export type ChorusWakeFn = (message: string, contextKey: string) => void;
|
|
29
|
+
export type ChorusWakeFn = (message: string, contextKey: string, attribution?: WakeAttribution) => void;
|
|
14
30
|
export interface ChorusEventRouterOptions {
|
|
15
31
|
mcpClient: ChorusMcpClient;
|
|
16
32
|
wake: ChorusWakeFn;
|
|
33
|
+
/**
|
|
34
|
+
* Optional lineage resolver (daemon parity). When present, the router resolves each
|
|
35
|
+
* notification's `{ rootIdeaUuid, directIdeaUuid }` via the root-idea REST endpoint
|
|
36
|
+
* before waking, so the daemon client can anchor the session on the direct idea and
|
|
37
|
+
* report the root idea in its execution snapshot. When absent (a host with no daemon
|
|
38
|
+
* reporting), wakes carry only the entity fields the notification already provides.
|
|
39
|
+
*/
|
|
40
|
+
lineage?: LineageResolver;
|
|
17
41
|
logger: {
|
|
18
42
|
info: (msg: string) => void;
|
|
19
43
|
warn: (msg: string) => void;
|
|
@@ -23,6 +47,7 @@ export interface ChorusEventRouterOptions {
|
|
|
23
47
|
export declare class ChorusEventRouter {
|
|
24
48
|
private readonly mcpClient;
|
|
25
49
|
private readonly wake;
|
|
50
|
+
private readonly lineage?;
|
|
26
51
|
private readonly logger;
|
|
27
52
|
constructor(opts: ChorusEventRouterOptions);
|
|
28
53
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-router.d.ts","sourceRoot":"","sources":["../src/event-router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"event-router.d.ts","sourceRoot":"","sources":["../src/event-router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,YAAY,GAAG,CACzB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,WAAW,CAAC,EAAE,eAAe,KAC1B,IAAI,CAAC;AAEV,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,eAAe,CAAC;IAC3B,IAAI,EAAE,YAAY,CAAC;IACnB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,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;AAmBD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAkB;IAC5C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAe;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAkB;IAC3C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqC;gBAEhD,IAAI,EAAE,wBAAwB;IAO1C;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,oBAAoB,GAAG,IAAI;IAgC3C;;;;OAIG;IACH,OAAO,CAAC,aAAa;YAIP,aAAa;IAgF3B;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAO5B,OAAO,CAAC,kBAAkB;IAU1B,OAAO,CAAC,eAAe;IAYvB,OAAO,CAAC,0BAA0B;IAQlC,OAAO,CAAC,sBAAsB;IAa9B,OAAO,CAAC,sBAAsB;IAa9B,OAAO,CAAC,iBAAiB;IAYzB,OAAO,CAAC,kBAAkB;IAS1B,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,yBAAyB;CAclC"}
|
package/dist/event-router.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
export class ChorusEventRouter {
|
|
2
2
|
mcpClient;
|
|
3
3
|
wake;
|
|
4
|
+
lineage;
|
|
4
5
|
logger;
|
|
5
6
|
constructor(opts) {
|
|
6
7
|
this.mcpClient = opts.mcpClient;
|
|
7
8
|
this.wake = opts.wake;
|
|
9
|
+
this.lineage = opts.lineage;
|
|
8
10
|
this.logger = opts.logger;
|
|
9
11
|
}
|
|
10
12
|
/**
|
|
@@ -12,6 +14,15 @@ export class ChorusEventRouter {
|
|
|
12
14
|
* Never throws — all errors are caught and logged internally.
|
|
13
15
|
*/
|
|
14
16
|
dispatch(event) {
|
|
17
|
+
// The bidirectional daemon events `connection_registered` and `control` are
|
|
18
|
+
// forked upstream by the SSE listener (onConnectionId / onControl) and never
|
|
19
|
+
// reach the wake path. If one ever does arrive here (defense-in-depth), drop
|
|
20
|
+
// it SILENTLY — it is a known, handled-elsewhere event, NOT an unhandled one,
|
|
21
|
+
// so logging it as "ignored" would be misleading noise (and the spec requires
|
|
22
|
+
// connection_registered never be logged as ignored).
|
|
23
|
+
if (event.type === "connection_registered" || event.type === "control") {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
15
26
|
// Only handle new_notification events (ignore count_update, etc.)
|
|
16
27
|
if (event.type !== "new_notification") {
|
|
17
28
|
this.logger.info(`SSE event type "${event.type}" ignored`);
|
|
@@ -55,35 +66,55 @@ export class ChorusEventRouter {
|
|
|
55
66
|
this.logger.warn(`Notification ${notificationUuid} not found in unread list`);
|
|
56
67
|
return;
|
|
57
68
|
}
|
|
69
|
+
// Resolve the wake attribution ONCE per notification (daemon parity). The lineage
|
|
70
|
+
// resolver returns { rootIdeaUuid, directIdeaUuid } via the root-idea REST endpoint;
|
|
71
|
+
// both null when there's no idea ancestor or no resolver is wired. The entity
|
|
72
|
+
// fields always come straight off the notification. This threads through to the
|
|
73
|
+
// daemon client so it reports/anchors against the right session + resource.
|
|
74
|
+
let attribution = {
|
|
75
|
+
entityType: notification.entityType,
|
|
76
|
+
entityUuid: notification.entityUuid,
|
|
77
|
+
};
|
|
78
|
+
if (this.lineage) {
|
|
79
|
+
try {
|
|
80
|
+
const { rootIdeaUuid, directIdeaUuid } = await this.lineage.resolve(notification);
|
|
81
|
+
attribution = { ...attribution, rootIdeaUuid, directIdeaUuid };
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
// A lineage failure must not lose the wake — fall back to the entity-only
|
|
85
|
+
// attribution (the daemon client then anchors on the entity uuid). Logged.
|
|
86
|
+
this.logger.warn(`Lineage resolve failed for ${notification.entityType}:${notification.entityUuid}: ${err}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
58
89
|
// Route based on action (which corresponds to notificationType)
|
|
59
90
|
try {
|
|
60
91
|
switch (notification.action) {
|
|
61
92
|
case "task_assigned":
|
|
62
|
-
this.handleTaskAssigned(notification);
|
|
93
|
+
this.handleTaskAssigned(notification, attribution);
|
|
63
94
|
break;
|
|
64
95
|
case "mentioned":
|
|
65
|
-
this.handleMentioned(notification);
|
|
96
|
+
this.handleMentioned(notification, attribution);
|
|
66
97
|
break;
|
|
67
98
|
case "elaboration_requested":
|
|
68
|
-
this.handleElaborationRequested(notification);
|
|
99
|
+
this.handleElaborationRequested(notification, attribution);
|
|
69
100
|
break;
|
|
70
101
|
case "elaboration_answered":
|
|
71
|
-
this.handleElaborationAnswered(notification);
|
|
102
|
+
this.handleElaborationAnswered(notification, attribution);
|
|
72
103
|
break;
|
|
73
104
|
case "proposal_rejected":
|
|
74
|
-
this.handleProposalRejected(notification);
|
|
105
|
+
this.handleProposalRejected(notification, attribution);
|
|
75
106
|
break;
|
|
76
107
|
case "proposal_approved":
|
|
77
|
-
this.handleProposalApproved(notification);
|
|
108
|
+
this.handleProposalApproved(notification, attribution);
|
|
78
109
|
break;
|
|
79
110
|
case "idea_claimed":
|
|
80
|
-
this.handleIdeaClaimed(notification);
|
|
111
|
+
this.handleIdeaClaimed(notification, attribution);
|
|
81
112
|
break;
|
|
82
113
|
case "task_verified":
|
|
83
|
-
this.handleTaskVerified(notification);
|
|
114
|
+
this.handleTaskVerified(notification, attribution);
|
|
84
115
|
break;
|
|
85
116
|
case "task_reopened":
|
|
86
|
-
this.handleTaskReopened(notification);
|
|
117
|
+
this.handleTaskReopened(notification, attribution);
|
|
87
118
|
break;
|
|
88
119
|
default:
|
|
89
120
|
this.logger.info(`Unhandled notification action: "${notification.action}"`);
|
|
@@ -102,56 +133,56 @@ export class ChorusEventRouter {
|
|
|
102
133
|
return (`After completing your work, post a comment on this ${entityType} using chorus_add_comment with @mention:\n` +
|
|
103
134
|
`Use this exact mention format: @[${n.actorName}](${n.actorType}:${n.actorUuid})`);
|
|
104
135
|
}
|
|
105
|
-
handleTaskAssigned(n) {
|
|
136
|
+
handleTaskAssigned(n, attr) {
|
|
106
137
|
const mentionGuidance = this.buildMentionGuidance(n, "task");
|
|
107
|
-
this.wake(`[Chorus] Task assigned: ${n.entityTitle}. Task UUID: ${n.entityUuid}, Project UUID: ${n.projectUuid}. Use chorus_get_task to review the task, then chorus_claim_task to start work.\n${mentionGuidance}`, this.contextKeyFor("task_assigned", n.entityUuid));
|
|
138
|
+
this.wake(`[Chorus] Task assigned: ${n.entityTitle}. Task UUID: ${n.entityUuid}, Project UUID: ${n.projectUuid}. Use chorus_get_task to review the task, then chorus_claim_task to start work.\n${mentionGuidance}`, this.contextKeyFor("task_assigned", n.entityUuid), attr);
|
|
108
139
|
}
|
|
109
|
-
handleMentioned(n) {
|
|
140
|
+
handleMentioned(n, attr) {
|
|
110
141
|
const mentionGuidance = this.buildMentionGuidance(n, n.entityType);
|
|
111
142
|
this.wake(`[Chorus] You were @mentioned in ${n.entityType} '${n.entityTitle}' (entityType: ${n.entityType}, entityUuid: ${n.entityUuid}, projectUuid: ${n.projectUuid}): ${n.message}\n` +
|
|
112
143
|
`Review the ${n.entityType} content and use chorus_get_comments (targetType: "${n.entityType}", targetUuid: "${n.entityUuid}") to see the full conversation, then respond.\n` +
|
|
113
|
-
mentionGuidance, this.contextKeyFor("mentioned", n.entityUuid));
|
|
144
|
+
mentionGuidance, this.contextKeyFor("mentioned", n.entityUuid), attr);
|
|
114
145
|
}
|
|
115
|
-
handleElaborationRequested(n) {
|
|
116
|
-
this.wake(`[Chorus] Elaboration requested for idea '${n.entityTitle}' (ideaUuid: ${n.entityUuid}, projectUuid: ${n.projectUuid}). Use chorus_get_elaboration to review questions.`, this.contextKeyFor("elaboration_requested", n.entityUuid));
|
|
146
|
+
handleElaborationRequested(n, attr) {
|
|
147
|
+
this.wake(`[Chorus] Elaboration requested for idea '${n.entityTitle}' (ideaUuid: ${n.entityUuid}, projectUuid: ${n.projectUuid}). Use chorus_get_elaboration to review questions.`, this.contextKeyFor("elaboration_requested", n.entityUuid), attr);
|
|
117
148
|
}
|
|
118
|
-
handleProposalRejected(n) {
|
|
149
|
+
handleProposalRejected(n, attr) {
|
|
119
150
|
const mentionGuidance = this.buildMentionGuidance(n, "proposal");
|
|
120
151
|
this.wake(`[Chorus] Proposal '${n.entityTitle}' was REJECTED (proposalUuid: ${n.entityUuid}, projectUuid: ${n.projectUuid}). Review note: "${n.message}". ` +
|
|
121
152
|
`Use chorus_get_proposal to review the proposal, then fix issues with chorus_update_task_draft / chorus_update_document_draft. ` +
|
|
122
153
|
`After fixing, call chorus_validate_proposal then chorus_submit_proposal to resubmit.\n` +
|
|
123
|
-
mentionGuidance, this.contextKeyFor("proposal_rejected", n.entityUuid));
|
|
154
|
+
mentionGuidance, this.contextKeyFor("proposal_rejected", n.entityUuid), attr);
|
|
124
155
|
}
|
|
125
|
-
handleProposalApproved(n) {
|
|
156
|
+
handleProposalApproved(n, attr) {
|
|
126
157
|
const mentionGuidance = this.buildMentionGuidance(n, "proposal");
|
|
127
158
|
const reviewInfo = n.message.includes("Note: ") ? ` Review note: "${n.message.split("Note: ").pop()}"` : "";
|
|
128
159
|
this.wake(`[Chorus] Proposal '${n.entityTitle}' was APPROVED (proposalUuid: ${n.entityUuid}, projectUuid: ${n.projectUuid})!${reviewInfo} Documents and tasks have been created. ` +
|
|
129
160
|
`Use chorus_get_available_tasks with projectUuid: "${n.projectUuid}" to see the new tasks ready for work.\n` +
|
|
130
|
-
mentionGuidance, this.contextKeyFor("proposal_approved", n.entityUuid));
|
|
161
|
+
mentionGuidance, this.contextKeyFor("proposal_approved", n.entityUuid), attr);
|
|
131
162
|
}
|
|
132
|
-
handleIdeaClaimed(n) {
|
|
163
|
+
handleIdeaClaimed(n, attr) {
|
|
133
164
|
const mentionGuidance = this.buildMentionGuidance(n, "idea");
|
|
134
165
|
this.wake(`[Chorus] Idea '${n.entityTitle}' has been assigned to you (ideaUuid: ${n.entityUuid}, projectUuid: ${n.projectUuid}). ` +
|
|
135
166
|
`Use chorus_get_idea to review the idea, then chorus_claim_idea to start elaboration.\n` +
|
|
136
|
-
mentionGuidance, this.contextKeyFor("idea_claimed", n.entityUuid));
|
|
167
|
+
mentionGuidance, this.contextKeyFor("idea_claimed", n.entityUuid), attr);
|
|
137
168
|
}
|
|
138
|
-
handleTaskVerified(n) {
|
|
169
|
+
handleTaskVerified(n, attr) {
|
|
139
170
|
this.wake(`[Chorus] Task '${n.entityTitle}' has been verified and is now done (taskUuid: ${n.entityUuid}, projectUuid: ${n.projectUuid}). ` +
|
|
140
|
-
`Check if this unblocks other tasks: use chorus_get_unblocked_tasks with projectUuid "${n.projectUuid}" to find tasks that are now ready to start.`, this.contextKeyFor("task_verified", n.entityUuid));
|
|
171
|
+
`Check if this unblocks other tasks: use chorus_get_unblocked_tasks with projectUuid "${n.projectUuid}" to find tasks that are now ready to start.`, this.contextKeyFor("task_verified", n.entityUuid), attr);
|
|
141
172
|
}
|
|
142
|
-
handleTaskReopened(n) {
|
|
173
|
+
handleTaskReopened(n, attr) {
|
|
143
174
|
const mentionGuidance = this.buildMentionGuidance(n, "task");
|
|
144
175
|
this.wake(`[Chorus] Task '${n.entityTitle}' has been reopened and needs rework (taskUuid: ${n.entityUuid}, projectUuid: ${n.projectUuid}). ` +
|
|
145
|
-
`Use chorus_get_task to review the task and chorus_get_comments to see verification feedback, then fix the issues.\n${mentionGuidance}`, this.contextKeyFor("task_reopened", n.entityUuid));
|
|
176
|
+
`Use chorus_get_task to review the task and chorus_get_comments to see verification feedback, then fix the issues.\n${mentionGuidance}`, this.contextKeyFor("task_reopened", n.entityUuid), attr);
|
|
146
177
|
}
|
|
147
|
-
handleElaborationAnswered(n) {
|
|
178
|
+
handleElaborationAnswered(n, attr) {
|
|
148
179
|
const mentionGuidance = this.buildMentionGuidance(n, "idea");
|
|
149
180
|
this.wake(`[Chorus] Elaboration answers submitted for idea '${n.entityTitle}' (ideaUuid: ${n.entityUuid}, projectUuid: ${n.projectUuid}). ` +
|
|
150
181
|
`Review the answers with chorus_get_elaboration, then either:\n` +
|
|
151
182
|
`- Call chorus_validate_elaboration with empty issues [] to resolve and proceed to proposal creation\n` +
|
|
152
183
|
`- Call chorus_validate_elaboration with issues + followUpQuestions for another round\n\n` +
|
|
153
184
|
`After reviewing, @mention the answerer to ask if they have any further questions before you proceed.\n` +
|
|
154
|
-
mentionGuidance, this.contextKeyFor("elaboration_answered", n.entityUuid));
|
|
185
|
+
mentionGuidance, this.contextKeyFor("elaboration_answered", n.entityUuid), attr);
|
|
155
186
|
}
|
|
156
187
|
}
|
|
157
188
|
//# sourceMappingURL=event-router.js.map
|
package/dist/event-router.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-router.js","sourceRoot":"","sources":["../src/event-router.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"event-router.js","sourceRoot":"","sources":["../src/event-router.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAuCA,wBA0LG"}
|
package/dist/index.js
CHANGED
|
@@ -4,8 +4,13 @@ import { ensureChorusMcpServer } from "./mcp-registration.js";
|
|
|
4
4
|
import { ChorusMcpClient } from "./mcp-client.js";
|
|
5
5
|
import { ChorusSseListener } from "./sse-listener.js";
|
|
6
6
|
import { ChorusEventRouter } from "./event-router.js";
|
|
7
|
-
import {
|
|
7
|
+
import { resolveWakeRunContext } from "./wake.js";
|
|
8
8
|
import { registerChorusCommands } from "./commands.js";
|
|
9
|
+
import { ConnectionState } from "./connection-state.js";
|
|
10
|
+
import { createControlHandler } from "./control-handler.js";
|
|
11
|
+
import { createDaemonRestClient } from "./daemon-rest-client.js";
|
|
12
|
+
import { LineageResolver } from "./lineage.js";
|
|
13
|
+
import { OpenClawDaemonClient } from "./daemon-client.js";
|
|
9
14
|
/**
|
|
10
15
|
* JSON-Schema config contract for the Chorus plugin.
|
|
11
16
|
*
|
|
@@ -57,15 +62,92 @@ export default definePluginEntry({
|
|
|
57
62
|
// 4. Slim MCP client for the plugin's own synchronous calls (checkin,
|
|
58
63
|
// assignments, notifications back-fill).
|
|
59
64
|
const mcpClient = new ChorusMcpClient({ chorusUrl, apiKey, logger });
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
+
// 4b. Connection identity + reverse control channel + daemon reporting (parity).
|
|
66
|
+
// `connectionState` holds the DaemonConnection uuid the server reports
|
|
67
|
+
// post-handshake (captured by the listener's onConnectionId); it is the
|
|
68
|
+
// single source of truth for "which connection am I", read by the control
|
|
69
|
+
// handler's double-check AND the daemon REST reporter (lazily, so order
|
|
70
|
+
// doesn't matter — both predate the handshake).
|
|
71
|
+
const connectionState = new ConnectionState();
|
|
72
|
+
// The shared pure-REST daemon client owns the `/api/daemon/*` payload shapes
|
|
73
|
+
// (turn-advance / transcript / execution-state / report-interrupt /
|
|
74
|
+
// pending-turns). It reads the connectionUuid lazily from connectionState.
|
|
75
|
+
const restClient = createDaemonRestClient({
|
|
76
|
+
url: chorusUrl,
|
|
77
|
+
apiKey,
|
|
78
|
+
getConnectionUuid: () => connectionState.getConnectionUuid(),
|
|
79
|
+
logger,
|
|
80
|
+
});
|
|
81
|
+
// Lineage resolver: per-notification { rootIdeaUuid, directIdeaUuid } via the
|
|
82
|
+
// root-idea REST endpoint, so the daemon client anchors the session on the
|
|
83
|
+
// DIRECT idea (resume/deliver_turn continuity) and reports the ROOT idea in
|
|
84
|
+
// its execution snapshot (the two-id contract).
|
|
85
|
+
const lineage = new LineageResolver({ url: chorusUrl, apiKey, logger });
|
|
86
|
+
// The in-process daemon client wraps runEmbeddedAgent with full reporting,
|
|
87
|
+
// the AbortController registry (real mid-run interrupt), the execution
|
|
88
|
+
// snapshot source, deterministic session-key mapping, and the at-most-once
|
|
89
|
+
// pending-turns backfill. `resolveRunContext` is the ONE place that reaches
|
|
90
|
+
// into api.config/api.runtime (kept out of the client so it stays testable).
|
|
91
|
+
// `redispatch` resolves lineage for a synthetic resume so it continues the
|
|
92
|
+
// SAME session, then runs the wake; a delivered turn already carries its ids.
|
|
93
|
+
let daemonClient;
|
|
94
|
+
const redispatch = (req) => {
|
|
95
|
+
void (async () => {
|
|
96
|
+
let enriched = req;
|
|
97
|
+
// A resume only knows the entity — resolve its lineage so the wake anchors on
|
|
98
|
+
// the same business key (direct idea) the original run used. A delivered turn
|
|
99
|
+
// already carries directIdeaUuid, so we skip the round-trip when present.
|
|
100
|
+
if (req.directIdeaUuid == null && req.entityType && req.entityUuid) {
|
|
101
|
+
try {
|
|
102
|
+
const { rootIdeaUuid, directIdeaUuid } = await lineage.resolve({
|
|
103
|
+
entityType: req.entityType,
|
|
104
|
+
entityUuid: req.entityUuid,
|
|
105
|
+
});
|
|
106
|
+
enriched = { ...req, rootIdeaUuid, directIdeaUuid };
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
logger.warn(`[Chorus] resume lineage resolve failed: ${err}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
await daemonClient.runWake(enriched);
|
|
113
|
+
})();
|
|
114
|
+
};
|
|
115
|
+
daemonClient = new OpenClawDaemonClient({
|
|
116
|
+
restClient,
|
|
117
|
+
resolveRunContext: () => resolveWakeRunContext(api, logger),
|
|
118
|
+
redispatch,
|
|
119
|
+
// Build the prompt for a delivered human_instruction turn. The free-text body
|
|
120
|
+
// lives only on the turn (promptText); fall back to a generic nudge if absent.
|
|
121
|
+
buildTurnPrompt: (turn) => turn.promptText && turn.promptText.trim()
|
|
122
|
+
? `[Chorus] A human sent you an instruction in this conversation:\n\n${turn.promptText}`
|
|
123
|
+
: `[Chorus] A human sent you a new instruction in this conversation (session ${turn.sessionId}). Review the latest comments and respond.`,
|
|
124
|
+
logger,
|
|
125
|
+
});
|
|
126
|
+
// The control handler ROUTES verified control commands to the daemon client's
|
|
127
|
+
// behavior hooks (real abort / resume re-dispatch / pending-turns sweep),
|
|
128
|
+
// after its own double-check (own connection + held entity).
|
|
129
|
+
const controlHooks = daemonClient.controlHooks;
|
|
130
|
+
const onControl = createControlHandler({ connectionState, hooks: controlHooks, logger });
|
|
131
|
+
// 5. Event router. Wakes the agent in-process by running an embedded agent turn
|
|
132
|
+
// via the daemon client (which calls api.runtime.agent.runEmbeddedAgent and
|
|
133
|
+
// reports lifecycle/transcript). The router resolves each notification's
|
|
134
|
+
// lineage, then the daemon client's runWake gracefully DROPS (logs + returns)
|
|
135
|
+
// when it cannot run — it never throws, so the SSE service stays alive.
|
|
136
|
+
const wakeFn = (message, contextKey, attribution) => {
|
|
137
|
+
void daemonClient.runWake({
|
|
138
|
+
prompt: message,
|
|
139
|
+
contextKey,
|
|
140
|
+
entityType: attribution?.entityType,
|
|
141
|
+
entityUuid: attribution?.entityUuid,
|
|
142
|
+
directIdeaUuid: attribution?.directIdeaUuid,
|
|
143
|
+
rootIdeaUuid: attribution?.rootIdeaUuid,
|
|
144
|
+
});
|
|
145
|
+
};
|
|
65
146
|
const eventRouter = new ChorusEventRouter({
|
|
66
147
|
mcpClient,
|
|
67
148
|
logger,
|
|
68
|
-
|
|
149
|
+
lineage,
|
|
150
|
+
wake: wakeFn,
|
|
69
151
|
});
|
|
70
152
|
// 6. Background SSE service. The SSE socket opens only inside start(), which
|
|
71
153
|
// the host calls in full mode — keeping the heavy socket gated.
|
|
@@ -78,7 +160,23 @@ export default definePluginEntry({
|
|
|
78
160
|
apiKey,
|
|
79
161
|
logger,
|
|
80
162
|
onEvent: (event) => eventRouter.dispatch(event),
|
|
163
|
+
// Capture (and refresh on reconnect) the DaemonConnection identity the
|
|
164
|
+
// server reports post-handshake. NOT a wake — forked by the listener.
|
|
165
|
+
onConnectionId: (connectionUuid) => {
|
|
166
|
+
connectionState.setConnectionUuid(connectionUuid);
|
|
167
|
+
logger.info(`[Chorus] registered as daemon connection ${connectionUuid}`);
|
|
168
|
+
},
|
|
169
|
+
// Reverse control channel. The handler does the double-check and routes
|
|
170
|
+
// to the behavior hooks — NEVER the wake path.
|
|
171
|
+
onControl,
|
|
81
172
|
onReconnect: async () => {
|
|
173
|
+
// (1) Notification backfill — re-pull unread notifications missed during
|
|
174
|
+
// the gap (autonomous wakes). (2) Pending-turns backfill — re-derive
|
|
175
|
+
// this connection's unstarted human_instruction turns from the turn
|
|
176
|
+
// table and run each (the lost-deliver_turn-ping safety net). The two
|
|
177
|
+
// share the daemon client's seen-set so a turn is run at most once
|
|
178
|
+
// across live delivery + backfill. Each swallows its own errors so one
|
|
179
|
+
// failing source never aborts the other.
|
|
82
180
|
try {
|
|
83
181
|
const result = (await mcpClient.callTool("chorus_get_notifications", {
|
|
84
182
|
status: "unread",
|
|
@@ -92,6 +190,7 @@ export default definePluginEntry({
|
|
|
92
190
|
catch (err) {
|
|
93
191
|
logger.warn(`Failed to back-fill notifications: ${err}`);
|
|
94
192
|
}
|
|
193
|
+
await daemonClient.onReconnect();
|
|
95
194
|
},
|
|
96
195
|
});
|
|
97
196
|
await sseListener.connect();
|