@beeos-ai/device-agent 0.7.2 → 0.9.1

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 (56) hide show
  1. package/dist/app/fixed-session-id.d.ts +19 -0
  2. package/dist/app/fixed-session-id.js +26 -0
  3. package/dist/app/fixed-session-id.js.map +1 -0
  4. package/dist/app/jsonl-session-sweeper.d.ts +90 -0
  5. package/dist/app/jsonl-session-sweeper.js +162 -0
  6. package/dist/app/jsonl-session-sweeper.js.map +1 -0
  7. package/dist/app/jsonl-store.d.ts +10 -0
  8. package/dist/app/jsonl-store.js +10 -4
  9. package/dist/app/jsonl-store.js.map +1 -1
  10. package/dist/app/session-manager.d.ts +135 -1
  11. package/dist/app/session-manager.js +195 -12
  12. package/dist/app/session-manager.js.map +1 -1
  13. package/dist/bootstrap.js +157 -2
  14. package/dist/bootstrap.js.map +1 -1
  15. package/dist/config/loader.d.ts +4 -0
  16. package/dist/config/loader.js +20 -2
  17. package/dist/config/loader.js.map +1 -1
  18. package/dist/config/schema.d.ts +7 -0
  19. package/dist/config/schema.js +82 -0
  20. package/dist/config/schema.js.map +1 -1
  21. package/dist/fleet/domain/device.d.ts +17 -0
  22. package/dist/fleet/domain/device.js +1 -0
  23. package/dist/fleet/domain/device.js.map +1 -1
  24. package/dist/fleet/domain/fsm.d.ts +30 -1
  25. package/dist/fleet/domain/fsm.js +100 -4
  26. package/dist/fleet/domain/fsm.js.map +1 -1
  27. package/dist/fleet/supervisor.js +38 -18
  28. package/dist/fleet/supervisor.js.map +1 -1
  29. package/dist/inbound/cli/main.js +12 -0
  30. package/dist/inbound/cli/main.js.map +1 -1
  31. package/dist/messaging/token-provider.d.ts +43 -0
  32. package/dist/messaging/token-provider.js +65 -0
  33. package/dist/messaging/token-provider.js.map +1 -0
  34. package/dist/plugins/acp/index.d.ts +27 -30
  35. package/dist/plugins/acp/index.js +44 -137
  36. package/dist/plugins/acp/index.js.map +1 -1
  37. package/dist/plugins/message-bus/busy-reply.d.ts +39 -0
  38. package/dist/plugins/message-bus/busy-reply.js +30 -0
  39. package/dist/plugins/message-bus/busy-reply.js.map +1 -0
  40. package/dist/plugins/message-bus/cancel.d.ts +48 -0
  41. package/dist/plugins/message-bus/cancel.js +66 -0
  42. package/dist/plugins/message-bus/cancel.js.map +1 -0
  43. package/dist/plugins/message-bus/inbound.d.ts +71 -0
  44. package/dist/plugins/message-bus/inbound.js +180 -0
  45. package/dist/plugins/message-bus/inbound.js.map +1 -0
  46. package/dist/plugins/message-bus/index.d.ts +146 -0
  47. package/dist/plugins/message-bus/index.js +382 -0
  48. package/dist/plugins/message-bus/index.js.map +1 -0
  49. package/dist/plugins/message-bus/ms-channel-notifier.d.ts +101 -0
  50. package/dist/plugins/message-bus/ms-channel-notifier.js +181 -0
  51. package/dist/plugins/message-bus/ms-channel-notifier.js.map +1 -0
  52. package/dist/plugins/message-bus/ms-inflight-tracker.d.ts +36 -0
  53. package/dist/plugins/message-bus/ms-inflight-tracker.js +42 -0
  54. package/dist/plugins/message-bus/ms-inflight-tracker.js.map +1 -0
  55. package/dist/ports/plugin.d.ts +12 -0
  56. package/package.json +2 -1
@@ -0,0 +1,382 @@
1
+ /**
2
+ * Plugin — message-bus.
3
+ *
4
+ * Subscribes the device-agent to the BeeOS Message Service so it can
5
+ * receive `chat_message` envelopes from L0 / A2A / MCP / OpenAPI
6
+ * invocations and reply over `ch:{channelId}` with `agent_reply` /
7
+ * `agent_reply_delta` (in_reply_to = original message_id).
8
+ *
9
+ * Disabled by default — opt in by setting `BEEOS_MESSAGE_BUS_ENABLED=true`
10
+ * (or `--message-bus`) AND providing both `--agent-gateway-url` and
11
+ * an Ed25519 identity. Without those the plugin logs a one-line warning
12
+ * during `start()` and stays disabled — the rest of the agent (Bridge
13
+ * ACP, HTTP debug) still runs.
14
+ *
15
+ * Lifecycle:
16
+ * - `init` — read config, decide enabled / disabled.
17
+ * - `start` — kick off the connect loop. Returns IMMEDIATELY (does
18
+ * not block bootstrap on the network). The first connect attempt
19
+ * and any subsequent retries run in the background.
20
+ * - `stop` — cancel any pending retry, disconnect SDK, unsubscribe
21
+ * outbound listener, drop in-flight register.
22
+ *
23
+ * Failure mode: if `tokenProvider()` rejects (e.g. the device instance
24
+ * has not been provisioned with `agentPublicKey` yet), the plugin
25
+ * schedules a retry with exponential backoff (30s → 60s → 120s → 240s,
26
+ * capped at 300s) and keeps trying forever until either `connect`
27
+ * succeeds or `stop()` cancels the loop. This mirrors beeos-claw's
28
+ * `localServer.onceAgentMappingReady(...)` deferred-connect pattern —
29
+ * see `[index.ts:484-498](agents/beeos-claw/src/index.ts)`.
30
+ */
31
+ import { MessageSDK } from "@beeos-ai/message-sdk";
32
+ import { createDeviceMessagingTokenProvider } from "../../messaging/token-provider.js";
33
+ import { MessageSdkBusyReplyHandler } from "./busy-reply.js";
34
+ import { buildMessageBusCancelHandler, MESSAGE_TYPE_CHAT_CANCEL, } from "./cancel.js";
35
+ import { buildMessageBusInboundHandler } from "./inbound.js";
36
+ import { MsInflightTracker } from "./ms-inflight-tracker.js";
37
+ /**
38
+ * Backoff schedule for token / connect failures (milliseconds). Each
39
+ * entry is the delay BEFORE the next retry. The last entry caps the
40
+ * delay; once we hit the cap we keep retrying forever at that interval.
41
+ *
42
+ * Tunable via `MessageBusPluginOptions.retrySchedule` — tests pin a
43
+ * tighter schedule (e.g. `[5, 5, 5]`) to keep `vi.useFakeTimers()`
44
+ * runs cheap.
45
+ */
46
+ export const DEFAULT_RETRY_SCHEDULE_MS = Object.freeze([
47
+ 30_000,
48
+ 60_000,
49
+ 120_000,
50
+ 240_000,
51
+ 300_000,
52
+ ]);
53
+ /** Attempt indices (1-based) where a `warn` log is preferred over `debug`. */
54
+ const WARN_ATTEMPT_INDICES = new Set([1, 3, 5, 10]);
55
+ export class MessageBusPlugin {
56
+ opts;
57
+ name = "message-bus";
58
+ ctx;
59
+ sdk = null;
60
+ tracker = null;
61
+ resolvedPrincipalId = "";
62
+ // Connect-loop state.
63
+ stopped = false;
64
+ retryHandle = null;
65
+ // When the loop is sleeping in `sleep(ms)`, this resolver lets
66
+ // `stop()` (or any other test-level cancel) wake it up early with
67
+ // `cancelled = true`. Always cleared back to `null` once the resolver
68
+ // fires.
69
+ retryResolve = null;
70
+ connectAttempt = 0;
71
+ legacyDeprecationLogged = false;
72
+ // Resolves once the plugin has either successfully connected OR is
73
+ // permanently disabled. Tests await this to avoid racing the
74
+ // background loop. Production callers ignore it.
75
+ connectSettled = Promise.resolve();
76
+ resolveConnectSettled = () => undefined;
77
+ constructor(opts = {}) {
78
+ this.opts = opts;
79
+ }
80
+ async init(ctx) {
81
+ this.ctx = ctx;
82
+ if (!ctx.config.messageBus?.enabled) {
83
+ ctx.log.info("message-bus: disabled (set BEEOS_MESSAGE_BUS_ENABLED=true or --message-bus to enable)");
84
+ return;
85
+ }
86
+ if (!this.opts.keypair) {
87
+ ctx.log.warn("message-bus: enabled but no Ed25519 identity available — stays disabled");
88
+ return;
89
+ }
90
+ if (!ctx.config.agentGatewayUrl) {
91
+ ctx.log.warn("message-bus: enabled but no agentGatewayUrl configured — stays disabled");
92
+ return;
93
+ }
94
+ }
95
+ async start() {
96
+ const ctx = this.ctx;
97
+ if (!ctx)
98
+ return;
99
+ if (!ctx.config.messageBus?.enabled)
100
+ return;
101
+ if (!this.opts.keypair)
102
+ return;
103
+ if (!ctx.config.agentGatewayUrl)
104
+ return;
105
+ this.connectSettled = new Promise((resolve) => {
106
+ this.resolveConnectSettled = resolve;
107
+ });
108
+ // Kick off the loop. Do NOT await — the bootstrap should not block
109
+ // on a remote handshake. The loop reschedules itself via setTimeout
110
+ // until either `tryConnectOnce` returns true or `stop()` is called.
111
+ void this.runConnectLoop();
112
+ }
113
+ async stop() {
114
+ this.stopped = true;
115
+ this.cancelPendingRetry();
116
+ // Resolve any outstanding waiter so test code that awaits
117
+ // `whenSettled()` does not hang past stop().
118
+ this.resolveConnectSettled();
119
+ if (this.sdk) {
120
+ try {
121
+ await this.sdk.disconnect();
122
+ }
123
+ catch (err) {
124
+ this.ctx?.log.warn({ err: err instanceof Error ? err.message : String(err) }, "message-bus: disconnect failed");
125
+ }
126
+ this.sdk = null;
127
+ }
128
+ if (this.tracker) {
129
+ this.tracker.reset();
130
+ this.tracker = null;
131
+ }
132
+ }
133
+ // Test seam — exposes the resolved principal so integration tests can
134
+ // assert what the plugin actually subscribed to without poking at
135
+ // private state.
136
+ get principalId() {
137
+ return this.resolvedPrincipalId;
138
+ }
139
+ /**
140
+ * Test helper — resolves once `start()`'s connect loop has either
141
+ * successfully connected or `stop()` was called. Production code
142
+ * never needs this.
143
+ */
144
+ whenSettled() {
145
+ return this.connectSettled;
146
+ }
147
+ // ── Internals ──────────────────────────────────────────────────────────
148
+ async runConnectLoop() {
149
+ const ctx = this.ctx;
150
+ const log = ctx.log;
151
+ while (!this.stopped) {
152
+ this.connectAttempt += 1;
153
+ const ok = await this.tryConnectOnce(this.connectAttempt);
154
+ if (ok) {
155
+ this.resolveConnectSettled();
156
+ return;
157
+ }
158
+ if (this.stopped) {
159
+ this.resolveConnectSettled();
160
+ return;
161
+ }
162
+ const delayMs = this.nextDelayMs(this.connectAttempt);
163
+ this.logRetryScheduled(this.connectAttempt, delayMs, log);
164
+ const cancelled = await this.sleep(delayMs);
165
+ if (cancelled) {
166
+ this.resolveConnectSettled();
167
+ return;
168
+ }
169
+ }
170
+ this.resolveConnectSettled();
171
+ }
172
+ /**
173
+ * Single connect attempt. Returns `true` on success (sdk connected,
174
+ * handlers + listener installed) and `false` on any failure (caller
175
+ * should schedule a retry).
176
+ *
177
+ * Each attempt builds a FRESH `MessageSDK` instance — re-using one
178
+ * across reconnects would carry stale handlers and confuse the
179
+ * centrifuge-js internal state. The SDK has its own internal
180
+ * reconnect once `.connect()` succeeds; this loop is for the OPENING
181
+ * handshake only.
182
+ */
183
+ async tryConnectOnce(attempt) {
184
+ const ctx = this.ctx;
185
+ const log = ctx.log;
186
+ const keypair = this.opts.keypair;
187
+ const agentGatewayUrl = ctx.config.agentGatewayUrl;
188
+ const tokenProvider = this.opts.tokenProviderFactory?.({ agentGatewayUrl, keypair }) ??
189
+ createDeviceMessagingTokenProvider({ agentGatewayUrl, keypair });
190
+ const sdk = this.opts.sdkFactory?.(log) ??
191
+ new MessageSDK({
192
+ // No serviceUrl — agents talk to MS only via Agent Gateway
193
+ // proxy + Centrifugo (P6 boundary).
194
+ centrifugoUrl: ctx.config.messageBus.centrifugoUrl || undefined,
195
+ }, wrapLoggerForSdk(log));
196
+ let tok;
197
+ try {
198
+ tok = await tokenProvider(ctx.config.messageBus.principalIdOverride ?? "");
199
+ }
200
+ catch (err) {
201
+ this.logAttemptFailure(attempt, "messaging-token fetch failed", err, log);
202
+ return false;
203
+ }
204
+ const principalId = tok.principal_id;
205
+ if (!principalId) {
206
+ this.logAttemptFailure(attempt, "gateway returned empty principal_id", null, log);
207
+ return false;
208
+ }
209
+ const tracker = new MsInflightTracker();
210
+ const busyReply = new MessageSdkBusyReplyHandler({ sdk });
211
+ const sessionManager = ctx.sessionManager;
212
+ const chatHandler = buildMessageBusInboundHandler({
213
+ sessionManager,
214
+ sdk,
215
+ busyReply,
216
+ tracker,
217
+ logger: log,
218
+ localAgentId: this.opts.localAgentId,
219
+ source: "chat_message",
220
+ });
221
+ const legacyHandler = buildMessageBusInboundHandler({
222
+ sessionManager,
223
+ sdk,
224
+ busyReply,
225
+ tracker,
226
+ logger: log,
227
+ localAgentId: this.opts.localAgentId,
228
+ source: "agent_request",
229
+ });
230
+ sdk.onMessage("chat_message", async (env) => chatHandler(env));
231
+ // Gap F — fire a one-shot deprecation warn the FIRST time a legacy
232
+ // `agent_request` envelope arrives. Mirrors beeos-claw
233
+ // [`index.ts:551-557`](agents/beeos-claw/src/index.ts).
234
+ sdk.onMessage("agent_request", async (env) => {
235
+ if (!this.legacyDeprecationLogged) {
236
+ this.legacyDeprecationLogged = true;
237
+ log.warn({}, "message-bus: received legacy 'agent_request' envelope — this path is deprecated; please migrate upstream callers to 'chat_message' (L0 AgentInvocationService)");
238
+ }
239
+ await legacyHandler(env);
240
+ });
241
+ // Cooperative cancel: the platform pushes `chat_cancel` envelopes
242
+ // when the caller hangs up / explicitly cancels / times out. We
243
+ // address the in-flight turn IM-natively by `target_message_id`
244
+ // (== inbound chat_message.message_id) so the agent stays free of
245
+ // A2A / MCP / OpenAPI protocol knowledge.
246
+ const cancelHandler = buildMessageBusCancelHandler({
247
+ sessionManager,
248
+ tracker,
249
+ logger: log,
250
+ });
251
+ sdk.onMessage(MESSAGE_TYPE_CHAT_CANCEL, async (env) => cancelHandler(env));
252
+ sdk.onceConnected(() => {
253
+ log.info({
254
+ principalId,
255
+ centrifugoUrl: tok.centrifugo_url,
256
+ channels: tok.channels,
257
+ attempt,
258
+ }, "message-bus: connected");
259
+ });
260
+ try {
261
+ await sdk.connect(principalId, {
262
+ centrifugoUrl: tok.centrifugo_url,
263
+ tokenProvider,
264
+ });
265
+ }
266
+ catch (err) {
267
+ // Disconnect the half-opened SDK so the next retry starts clean.
268
+ try {
269
+ await sdk.disconnect();
270
+ }
271
+ catch {
272
+ /* ignore */
273
+ }
274
+ this.logAttemptFailure(attempt, "Centrifugo connect failed", err, log);
275
+ return false;
276
+ }
277
+ this.sdk = sdk;
278
+ this.tracker = tracker;
279
+ this.resolvedPrincipalId = principalId;
280
+ return true;
281
+ }
282
+ nextDelayMs(attempt) {
283
+ const schedule = this.opts.retrySchedule ?? DEFAULT_RETRY_SCHEDULE_MS;
284
+ const idx = Math.min(attempt - 1, schedule.length - 1);
285
+ return schedule[idx];
286
+ }
287
+ /**
288
+ * Log an attempt failure with attenuated severity — first attempt
289
+ * + a few well-spaced retries get a `warn`, the rest are `debug` to
290
+ * avoid log spam when an instance has never been provisioned.
291
+ */
292
+ logAttemptFailure(attempt, summary, err, log) {
293
+ const errStr = err == null
294
+ ? null
295
+ : err instanceof Error
296
+ ? err.message
297
+ : String(err);
298
+ const fields = { attempt };
299
+ if (errStr)
300
+ fields.err = errStr;
301
+ if (WARN_ATTEMPT_INDICES.has(attempt)) {
302
+ log.warn(fields, `message-bus: ${summary} — will retry`);
303
+ }
304
+ else {
305
+ log.debug(fields, `message-bus: ${summary} — will retry`);
306
+ }
307
+ }
308
+ logRetryScheduled(attempt, delayMs, log) {
309
+ if (WARN_ATTEMPT_INDICES.has(attempt)) {
310
+ log.warn({ attempt, retryInMs: delayMs }, "message-bus: scheduled connect retry");
311
+ }
312
+ }
313
+ /**
314
+ * Sleep `ms` milliseconds. Returns `true` when stop() cancels the
315
+ * timer mid-flight (so the caller breaks out of its loop), or
316
+ * `false` once the timer fires normally.
317
+ */
318
+ sleep(ms) {
319
+ const sched = this.scheduler();
320
+ return new Promise((resolve) => {
321
+ this.retryResolve = resolve;
322
+ this.retryHandle = sched.setTimeout(() => {
323
+ this.retryHandle = null;
324
+ this.retryResolve = null;
325
+ resolve(false);
326
+ }, ms);
327
+ });
328
+ }
329
+ cancelPendingRetry() {
330
+ if (this.retryHandle == null)
331
+ return;
332
+ const sched = this.scheduler();
333
+ try {
334
+ sched.clearTimeout(this.retryHandle);
335
+ }
336
+ catch {
337
+ /* ignore */
338
+ }
339
+ this.retryHandle = null;
340
+ const resolve = this.retryResolve;
341
+ this.retryResolve = null;
342
+ if (resolve)
343
+ resolve(true);
344
+ }
345
+ scheduler() {
346
+ return (this.opts.scheduler ?? {
347
+ setTimeout: (cb, t) => setTimeout(cb, t),
348
+ clearTimeout: (h) => clearTimeout(h),
349
+ });
350
+ }
351
+ }
352
+ /**
353
+ * Adapt the device-agent pino logger to the MessageSDKLogger shape the
354
+ * shared SDK expects. The SDK calls `info(msg)` / `warn(msg, fields?)`
355
+ * with positional args; pino's signature is `(obj, msg)` so we coerce
356
+ * to a stringly form that prints sanely either way.
357
+ */
358
+ function wrapLoggerForSdk(log) {
359
+ return {
360
+ info: (...args) => log.info(formatSdkArgs(args)),
361
+ warn: (...args) => log.warn(formatSdkArgs(args)),
362
+ error: (...args) => log.error(formatSdkArgs(args)),
363
+ debug: (...args) => log.debug(formatSdkArgs(args)),
364
+ };
365
+ }
366
+ function formatSdkArgs(args) {
367
+ return args
368
+ .map((a) => {
369
+ if (typeof a === "string")
370
+ return a;
371
+ if (a instanceof Error)
372
+ return a.message;
373
+ try {
374
+ return JSON.stringify(a);
375
+ }
376
+ catch {
377
+ return String(a);
378
+ }
379
+ })
380
+ .join(" ");
381
+ }
382
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/plugins/message-bus/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * MsChannelNotifier — translate per-session ACP `session/update`
3
+ * envelopes into BeeOS Message Service wire envelopes for ONE
4
+ * Centrifugo channel (`ch:{channelId}`).
5
+ *
6
+ * Implements `SessionUpdateNotifier` so it can be `bindNotifier`'d
7
+ * to a specific sid in `SessionManager`. The MessageBus inbound
8
+ * handler creates one of these per `chat_message` envelope, binds
9
+ * it for the duration of the turn, then unbinds it when the await
10
+ * for `promptForSession()` returns.
11
+ *
12
+ * Wire mapping (locked in to match beeos-claw `acp-server.ts onStreamDelta`):
13
+ *
14
+ * - `agent_message_chunk` → `agent_reply_delta` envelope with
15
+ * `update.sessionUpdate=agent_message_chunk`. The text is also
16
+ * accumulated into `accumulatedText` so the terminal `agent_reply`
17
+ * can carry the full reply as `payload.text`.
18
+ *
19
+ * - `agent_thought_chunk` → `agent_reply_delta` envelope with
20
+ * `update.sessionUpdate=agent_thought_chunk`. Reasoning is NEVER
21
+ * accumulated — `backend/pkg/convmsg/extract.go::extractNestedDelta`
22
+ * intentionally drops thought chunks so they don't pollute reply
23
+ * text, and we mirror that here.
24
+ *
25
+ * - `stream_complete` → terminal `agent_reply` envelope. Payload
26
+ * shape mirrors beeos-claw:
27
+ * success → { session_key, text }
28
+ * cancelled → { session_key, error: "cancelled", text? }
29
+ * failure → { session_key, error, text? }
30
+ *
31
+ * - All other `sessionUpdate` types (`agent_status`, `tool_call`,
32
+ * `tool_call_update`, `history_turn`, `system_message_chunk`,
33
+ * `user_message_chunk`) are intentionally SKIPPED. The old
34
+ * outbound listener also skipped them; mirroring keeps the wire
35
+ * format byte-for-byte identical for downstream consumers.
36
+ *
37
+ * **Only the terminal `agent_reply`** carries `in_reply_to = inboundMessageId`
38
+ * so the L0 `/channels/{id}/wait` endpoint can match the reply on the
39
+ * original `message_id`. Streaming deltas are fan-out only and MUST NOT
40
+ * carry `in_reply_to` — otherwise MS `/wait` (which only checks
41
+ * `in_reply_to`, not message type) would short-circuit on the first
42
+ * delta and the caller would misreport "completed". Mirrors beeos-claw
43
+ * `acp-server.ts:73-85`.
44
+ *
45
+ * The notifier never throws out of `send()` — that runs on the
46
+ * SessionManager event hot path and a synchronous throw would skip
47
+ * subsequent handlers (e.g. status:idle). All publish failures are
48
+ * logged and swallowed. The next event for the same turn will retry
49
+ * any subsequent publishes via the same path.
50
+ */
51
+ import type { MessageSDK } from "@beeos-ai/message-sdk";
52
+ import type { SessionUpdateNotifier } from "../../app/session-manager.js";
53
+ import type { Logger } from "../../logger/index.js";
54
+ export interface MsChannelNotifierOptions {
55
+ sdk: MessageSDK;
56
+ /** IM channel the reply is published on (`ch:{channelId}`). */
57
+ channelId: string;
58
+ /**
59
+ * Original `chat_message.message_id`. Echoed as `in_reply_to` on
60
+ * every published envelope so L0 `/wait` can match.
61
+ */
62
+ inboundMessageId: string;
63
+ /**
64
+ * Stable session anchor (`agent:{id}:invoke:ctx-{contextId}` or
65
+ * `agent:{id}:invoke:ch-{channelId}`) derived by `extractChatPrompt`.
66
+ * Embedded as `payload.session_key` for forward-compat parity with
67
+ * beeos-claw downstream observers.
68
+ */
69
+ sessionKey: string;
70
+ logger: Logger;
71
+ }
72
+ export declare class MsChannelNotifier implements SessionUpdateNotifier {
73
+ private readonly opts;
74
+ /** Cumulative `agent_message_chunk` text — populates terminal `agent_reply.text`. */
75
+ private accumulatedText;
76
+ private finalized;
77
+ constructor(opts: MsChannelNotifierOptions);
78
+ /**
79
+ * Called by SessionManager for every `session/update` envelope on
80
+ * this notifier's bound sid. Fire-and-forget: the SessionManager
81
+ * event hot path is synchronous, so we kick off the publish in the
82
+ * background and never block.
83
+ */
84
+ send(_sessionId: string, update: Record<string, unknown>): void;
85
+ /**
86
+ * Cumulative reply text emitted so far. Exposed for diagnostics +
87
+ * tests; not part of any wire contract.
88
+ */
89
+ get currentText(): string;
90
+ /** True once the terminal `agent_reply` has been published. */
91
+ get isFinalized(): boolean;
92
+ private handle;
93
+ private publishDelta;
94
+ /**
95
+ * Emit the terminal `agent_reply`. Idempotent — guarded by
96
+ * `finalized` so a duplicate `stream_complete` (extremely rare,
97
+ * but possible if the host event handler ever fires twice) cannot
98
+ * publish two terminal envelopes.
99
+ */
100
+ private publishFinal;
101
+ }
@@ -0,0 +1,181 @@
1
+ /**
2
+ * MsChannelNotifier — translate per-session ACP `session/update`
3
+ * envelopes into BeeOS Message Service wire envelopes for ONE
4
+ * Centrifugo channel (`ch:{channelId}`).
5
+ *
6
+ * Implements `SessionUpdateNotifier` so it can be `bindNotifier`'d
7
+ * to a specific sid in `SessionManager`. The MessageBus inbound
8
+ * handler creates one of these per `chat_message` envelope, binds
9
+ * it for the duration of the turn, then unbinds it when the await
10
+ * for `promptForSession()` returns.
11
+ *
12
+ * Wire mapping (locked in to match beeos-claw `acp-server.ts onStreamDelta`):
13
+ *
14
+ * - `agent_message_chunk` → `agent_reply_delta` envelope with
15
+ * `update.sessionUpdate=agent_message_chunk`. The text is also
16
+ * accumulated into `accumulatedText` so the terminal `agent_reply`
17
+ * can carry the full reply as `payload.text`.
18
+ *
19
+ * - `agent_thought_chunk` → `agent_reply_delta` envelope with
20
+ * `update.sessionUpdate=agent_thought_chunk`. Reasoning is NEVER
21
+ * accumulated — `backend/pkg/convmsg/extract.go::extractNestedDelta`
22
+ * intentionally drops thought chunks so they don't pollute reply
23
+ * text, and we mirror that here.
24
+ *
25
+ * - `stream_complete` → terminal `agent_reply` envelope. Payload
26
+ * shape mirrors beeos-claw:
27
+ * success → { session_key, text }
28
+ * cancelled → { session_key, error: "cancelled", text? }
29
+ * failure → { session_key, error, text? }
30
+ *
31
+ * - All other `sessionUpdate` types (`agent_status`, `tool_call`,
32
+ * `tool_call_update`, `history_turn`, `system_message_chunk`,
33
+ * `user_message_chunk`) are intentionally SKIPPED. The old
34
+ * outbound listener also skipped them; mirroring keeps the wire
35
+ * format byte-for-byte identical for downstream consumers.
36
+ *
37
+ * **Only the terminal `agent_reply`** carries `in_reply_to = inboundMessageId`
38
+ * so the L0 `/channels/{id}/wait` endpoint can match the reply on the
39
+ * original `message_id`. Streaming deltas are fan-out only and MUST NOT
40
+ * carry `in_reply_to` — otherwise MS `/wait` (which only checks
41
+ * `in_reply_to`, not message type) would short-circuit on the first
42
+ * delta and the caller would misreport "completed". Mirrors beeos-claw
43
+ * `acp-server.ts:73-85`.
44
+ *
45
+ * The notifier never throws out of `send()` — that runs on the
46
+ * SessionManager event hot path and a synchronous throw would skip
47
+ * subsequent handlers (e.g. status:idle). All publish failures are
48
+ * logged and swallowed. The next event for the same turn will retry
49
+ * any subsequent publishes via the same path.
50
+ */
51
+ export class MsChannelNotifier {
52
+ opts;
53
+ /** Cumulative `agent_message_chunk` text — populates terminal `agent_reply.text`. */
54
+ accumulatedText = "";
55
+ finalized = false;
56
+ constructor(opts) {
57
+ this.opts = opts;
58
+ }
59
+ /**
60
+ * Called by SessionManager for every `session/update` envelope on
61
+ * this notifier's bound sid. Fire-and-forget: the SessionManager
62
+ * event hot path is synchronous, so we kick off the publish in the
63
+ * background and never block.
64
+ */
65
+ send(_sessionId, update) {
66
+ void this.handle(update);
67
+ }
68
+ /**
69
+ * Cumulative reply text emitted so far. Exposed for diagnostics +
70
+ * tests; not part of any wire contract.
71
+ */
72
+ get currentText() {
73
+ return this.accumulatedText;
74
+ }
75
+ /** True once the terminal `agent_reply` has been published. */
76
+ get isFinalized() {
77
+ return this.finalized;
78
+ }
79
+ async handle(update) {
80
+ const sessionUpdate = pickSessionUpdate(update);
81
+ try {
82
+ switch (sessionUpdate) {
83
+ case "agent_message_chunk": {
84
+ const text = pickContentText(update.content);
85
+ if (!text)
86
+ return;
87
+ this.accumulatedText += text;
88
+ await this.publishDelta("agent_message_chunk", text);
89
+ return;
90
+ }
91
+ case "agent_thought_chunk": {
92
+ const text = pickContentText(update.content);
93
+ if (!text)
94
+ return;
95
+ await this.publishDelta("agent_thought_chunk", text);
96
+ return;
97
+ }
98
+ case "stream_complete": {
99
+ const stopReason = pickString(update.stopReason) || "end_turn";
100
+ const errorText = pickString(update.error);
101
+ await this.publishFinal(stopReason, errorText);
102
+ return;
103
+ }
104
+ default:
105
+ // agent_status / tool_call / tool_call_update / history_turn
106
+ // / system_message_chunk / user_message_chunk → skip.
107
+ return;
108
+ }
109
+ }
110
+ catch (err) {
111
+ this.opts.logger.error({
112
+ err: err instanceof Error ? err.message : String(err),
113
+ channelId: this.opts.channelId,
114
+ sessionUpdate,
115
+ }, "ms-channel-notifier: publish failed");
116
+ }
117
+ }
118
+ async publishDelta(sessionUpdate, text) {
119
+ // Streaming deltas are fan-out only — they MUST NOT carry
120
+ // `in_reply_to`, otherwise MS `/wait` (which only checks
121
+ // `in_reply_to`, not message type) would short-circuit on the
122
+ // first delta and the caller would think the turn finished.
123
+ // Mirrors beeos-claw `acp-server.ts:73-85`.
124
+ await this.opts.sdk.sendToChannel(this.opts.channelId, "agent_reply_delta", {
125
+ session_key: this.opts.sessionKey,
126
+ update: {
127
+ sessionUpdate,
128
+ content: { type: "text", text },
129
+ },
130
+ });
131
+ }
132
+ /**
133
+ * Emit the terminal `agent_reply`. Idempotent — guarded by
134
+ * `finalized` so a duplicate `stream_complete` (extremely rare,
135
+ * but possible if the host event handler ever fires twice) cannot
136
+ * publish two terminal envelopes.
137
+ */
138
+ async publishFinal(stopReason, errorText) {
139
+ if (this.finalized)
140
+ return;
141
+ this.finalized = true;
142
+ let payload;
143
+ if (stopReason === "cancelled") {
144
+ payload = {
145
+ session_key: this.opts.sessionKey,
146
+ error: "cancelled",
147
+ text: this.accumulatedText,
148
+ };
149
+ }
150
+ else if (errorText) {
151
+ payload = {
152
+ session_key: this.opts.sessionKey,
153
+ error: errorText,
154
+ text: this.accumulatedText,
155
+ };
156
+ }
157
+ else {
158
+ payload = {
159
+ session_key: this.opts.sessionKey,
160
+ text: this.accumulatedText,
161
+ };
162
+ }
163
+ await this.opts.sdk.sendToChannel(this.opts.channelId, "agent_reply", payload, { inReplyTo: this.opts.inboundMessageId });
164
+ }
165
+ }
166
+ function pickSessionUpdate(update) {
167
+ const v = update.sessionUpdate;
168
+ return typeof v === "string" ? v : "";
169
+ }
170
+ function pickContentText(content) {
171
+ if (!content || typeof content !== "object")
172
+ return "";
173
+ const c = content;
174
+ if (typeof c.text === "string")
175
+ return c.text;
176
+ return "";
177
+ }
178
+ function pickString(v) {
179
+ return typeof v === "string" ? v : "";
180
+ }
181
+ //# sourceMappingURL=ms-channel-notifier.js.map