heyhank 0.1.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 (199) hide show
  1. package/README.md +40 -0
  2. package/bin/cli.ts +168 -0
  3. package/bin/ctl.ts +528 -0
  4. package/bin/generate-token.ts +28 -0
  5. package/dist/apple-touch-icon.png +0 -0
  6. package/dist/assets/AgentsPage-BPhirnCe.js +7 -0
  7. package/dist/assets/AssistantPage-DJ-cMQfb.js +1 -0
  8. package/dist/assets/CronManager-DDbz-yiT.js +1 -0
  9. package/dist/assets/HelpPage-DMfkzERp.js +1 -0
  10. package/dist/assets/IntegrationsPage-CrOitCmJ.js +1 -0
  11. package/dist/assets/MediaPage-CE5rdvkC.js +1 -0
  12. package/dist/assets/PlatformDashboard-Do6F0O2p.js +1 -0
  13. package/dist/assets/Playground-Fc5cdc5p.js +109 -0
  14. package/dist/assets/ProcessPanel-CslEiZkI.js +2 -0
  15. package/dist/assets/PromptsPage-D2EhsdNO.js +4 -0
  16. package/dist/assets/RunsPage-C5BZF5Rx.js +1 -0
  17. package/dist/assets/SandboxManager-a1AVI5q2.js +8 -0
  18. package/dist/assets/SettingsPage-DirhjQrJ.js +51 -0
  19. package/dist/assets/SocialMediaPage-DBuM28vD.js +1 -0
  20. package/dist/assets/TailscalePage-CHiFhZXF.js +1 -0
  21. package/dist/assets/TelephonyPage-x0VV0fOo.js +1 -0
  22. package/dist/assets/TerminalPage-Drwyrnfd.js +1 -0
  23. package/dist/assets/gemini-audio-t-TSU-To.js +17 -0
  24. package/dist/assets/gemini-live-client-C7rqAW7G.js +166 -0
  25. package/dist/assets/index-C8M_PUmX.css +32 -0
  26. package/dist/assets/index-CEqZnThB.js +204 -0
  27. package/dist/assets/sw-register-LSSpj6RU.js +1 -0
  28. package/dist/assets/time-ago-B6r_l9u1.js +1 -0
  29. package/dist/assets/workbox-window.prod.es5-BIl4cyR9.js +2 -0
  30. package/dist/favicon-32-original.png +0 -0
  31. package/dist/favicon-32.png +0 -0
  32. package/dist/favicon.ico +0 -0
  33. package/dist/favicon.svg +8 -0
  34. package/dist/fonts/MesloLGSNerdFontMono-Bold.woff2 +0 -0
  35. package/dist/fonts/MesloLGSNerdFontMono-Regular.woff2 +0 -0
  36. package/dist/heyhank-mascot-poster.png +0 -0
  37. package/dist/heyhank-mascot.mp4 +0 -0
  38. package/dist/heyhank-mascot.webm +0 -0
  39. package/dist/icon-192-original.png +0 -0
  40. package/dist/icon-192.png +0 -0
  41. package/dist/icon-512-original.png +0 -0
  42. package/dist/icon-512.png +0 -0
  43. package/dist/index.html +21 -0
  44. package/dist/logo-192.png +0 -0
  45. package/dist/logo-512.png +0 -0
  46. package/dist/logo-codex.svg +14 -0
  47. package/dist/logo-docker.svg +4 -0
  48. package/dist/logo-original.png +0 -0
  49. package/dist/logo.png +0 -0
  50. package/dist/logo.svg +14 -0
  51. package/dist/manifest.json +24 -0
  52. package/dist/push-sw.js +34 -0
  53. package/dist/sw.js +1 -0
  54. package/dist/workbox-d2a0910a.js +1 -0
  55. package/package.json +109 -0
  56. package/server/agent-cron-migrator.ts +85 -0
  57. package/server/agent-executor.ts +357 -0
  58. package/server/agent-store.ts +185 -0
  59. package/server/agent-timeout.ts +107 -0
  60. package/server/agent-types.ts +122 -0
  61. package/server/ai-validation-settings.ts +37 -0
  62. package/server/ai-validator.ts +181 -0
  63. package/server/anthropic-provider-migration.ts +48 -0
  64. package/server/assistant-store.ts +272 -0
  65. package/server/auth-manager.ts +150 -0
  66. package/server/auto-approve.ts +153 -0
  67. package/server/auto-namer.ts +36 -0
  68. package/server/backend-adapter.ts +54 -0
  69. package/server/cache-headers.ts +61 -0
  70. package/server/calendar-service.ts +434 -0
  71. package/server/claude-adapter.ts +889 -0
  72. package/server/claude-container-auth.ts +30 -0
  73. package/server/claude-session-discovery.ts +157 -0
  74. package/server/claude-session-history.ts +410 -0
  75. package/server/cli-launcher.ts +1303 -0
  76. package/server/codex-adapter.ts +3027 -0
  77. package/server/codex-container-auth.ts +24 -0
  78. package/server/codex-home.ts +27 -0
  79. package/server/codex-ws-proxy.cjs +226 -0
  80. package/server/commands-discovery.ts +81 -0
  81. package/server/constants.ts +7 -0
  82. package/server/container-manager.ts +1053 -0
  83. package/server/cost-tracker.ts +222 -0
  84. package/server/cron-scheduler.ts +243 -0
  85. package/server/cron-store.ts +148 -0
  86. package/server/cron-types.ts +63 -0
  87. package/server/email-service.ts +354 -0
  88. package/server/env-manager.ts +161 -0
  89. package/server/event-bus-types.ts +75 -0
  90. package/server/event-bus.ts +124 -0
  91. package/server/execution-store.ts +170 -0
  92. package/server/federation/node-connection.ts +190 -0
  93. package/server/federation/node-manager.ts +366 -0
  94. package/server/federation/node-store.ts +86 -0
  95. package/server/federation/node-types.ts +121 -0
  96. package/server/fs-utils.ts +15 -0
  97. package/server/git-utils.ts +421 -0
  98. package/server/github-pr.ts +379 -0
  99. package/server/google-media.ts +342 -0
  100. package/server/image-pull-manager.ts +279 -0
  101. package/server/index.ts +491 -0
  102. package/server/internal-ai.ts +237 -0
  103. package/server/kill-switch.ts +99 -0
  104. package/server/llm-providers.ts +342 -0
  105. package/server/logger.ts +259 -0
  106. package/server/mcp-registry.ts +401 -0
  107. package/server/message-bus.ts +271 -0
  108. package/server/message-delivery.ts +128 -0
  109. package/server/metrics-collector.ts +350 -0
  110. package/server/metrics-types.ts +108 -0
  111. package/server/middleware/managed-auth.ts +195 -0
  112. package/server/novnc-proxy.ts +99 -0
  113. package/server/path-resolver.ts +186 -0
  114. package/server/paths.ts +13 -0
  115. package/server/pr-poller.ts +162 -0
  116. package/server/prompt-manager.ts +211 -0
  117. package/server/protocol/claude-upstream/README.md +19 -0
  118. package/server/protocol/claude-upstream/sdk.d.ts.txt +1943 -0
  119. package/server/protocol/codex-upstream/ClientNotification.ts.txt +5 -0
  120. package/server/protocol/codex-upstream/ClientRequest.ts.txt +60 -0
  121. package/server/protocol/codex-upstream/README.md +18 -0
  122. package/server/protocol/codex-upstream/ServerNotification.ts.txt +41 -0
  123. package/server/protocol/codex-upstream/ServerRequest.ts.txt +16 -0
  124. package/server/protocol/codex-upstream/v2/DynamicToolCallParams.ts.txt +6 -0
  125. package/server/protocol/codex-upstream/v2/DynamicToolCallResponse.ts.txt +6 -0
  126. package/server/protocol-monitor.ts +50 -0
  127. package/server/provider-manager.ts +111 -0
  128. package/server/provider-registry.ts +393 -0
  129. package/server/push-notifications.ts +221 -0
  130. package/server/recorder.ts +374 -0
  131. package/server/recording-hub/compat-validator.ts +284 -0
  132. package/server/recording-hub/diagnostics.ts +299 -0
  133. package/server/recording-hub/hub-config.ts +19 -0
  134. package/server/recording-hub/hub-routes.ts +236 -0
  135. package/server/recording-hub/hub-store.ts +265 -0
  136. package/server/recording-hub/replay-adapter.ts +207 -0
  137. package/server/relay-client.ts +320 -0
  138. package/server/reminder-scheduler.ts +38 -0
  139. package/server/replay.ts +78 -0
  140. package/server/routes/agent-routes.ts +264 -0
  141. package/server/routes/assistant-routes.ts +90 -0
  142. package/server/routes/cron-routes.ts +103 -0
  143. package/server/routes/env-routes.ts +95 -0
  144. package/server/routes/federation-routes.ts +76 -0
  145. package/server/routes/fs-routes.ts +622 -0
  146. package/server/routes/git-routes.ts +97 -0
  147. package/server/routes/llm-routes.ts +166 -0
  148. package/server/routes/media-routes.ts +135 -0
  149. package/server/routes/metrics-routes.ts +13 -0
  150. package/server/routes/platform-routes.ts +1379 -0
  151. package/server/routes/prompt-routes.ts +67 -0
  152. package/server/routes/provider-routes.ts +109 -0
  153. package/server/routes/sandbox-routes.ts +127 -0
  154. package/server/routes/settings-routes.ts +285 -0
  155. package/server/routes/skills-routes.ts +100 -0
  156. package/server/routes/socialmedia-routes.ts +208 -0
  157. package/server/routes/system-routes.ts +228 -0
  158. package/server/routes/tailscale-routes.ts +22 -0
  159. package/server/routes/telephony-routes.ts +259 -0
  160. package/server/routes.ts +1379 -0
  161. package/server/sandbox-manager.ts +168 -0
  162. package/server/service.ts +718 -0
  163. package/server/session-creation-service.ts +457 -0
  164. package/server/session-git-info.ts +104 -0
  165. package/server/session-names.ts +67 -0
  166. package/server/session-orchestrator.ts +824 -0
  167. package/server/session-state-machine.ts +207 -0
  168. package/server/session-store.ts +146 -0
  169. package/server/session-types.ts +511 -0
  170. package/server/settings-manager.ts +149 -0
  171. package/server/shared-context.ts +157 -0
  172. package/server/socialmedia/adapter.ts +15 -0
  173. package/server/socialmedia/adapters/ayrshare-adapter.ts +169 -0
  174. package/server/socialmedia/adapters/buffer-adapter.ts +299 -0
  175. package/server/socialmedia/adapters/postiz-adapter.ts +298 -0
  176. package/server/socialmedia/manager.ts +227 -0
  177. package/server/socialmedia/store.ts +98 -0
  178. package/server/socialmedia/types.ts +89 -0
  179. package/server/tailscale-manager.ts +451 -0
  180. package/server/telephony/audio-bridge.ts +331 -0
  181. package/server/telephony/call-manager.ts +457 -0
  182. package/server/telephony/call-types.ts +108 -0
  183. package/server/telephony/telephony-store.ts +119 -0
  184. package/server/terminal-manager.ts +240 -0
  185. package/server/update-checker.ts +192 -0
  186. package/server/usage-limits.ts +225 -0
  187. package/server/web-push.d.ts +51 -0
  188. package/server/worktree-tracker.ts +84 -0
  189. package/server/ws-auth.ts +41 -0
  190. package/server/ws-bridge-browser-ingest.ts +72 -0
  191. package/server/ws-bridge-browser.ts +112 -0
  192. package/server/ws-bridge-cli-ingest.ts +81 -0
  193. package/server/ws-bridge-codex.ts +266 -0
  194. package/server/ws-bridge-controls.ts +20 -0
  195. package/server/ws-bridge-persist.ts +66 -0
  196. package/server/ws-bridge-publish.ts +79 -0
  197. package/server/ws-bridge-replay.ts +61 -0
  198. package/server/ws-bridge-types.ts +121 -0
  199. package/server/ws-bridge.ts +1240 -0
@@ -0,0 +1,271 @@
1
+ // ─── Inter-Agent Message Bus ─────────────────────────────────────────────────
2
+ // Ported from AgentManager/src/messages.ts — adapted for HeyHank/Bun
3
+
4
+ import { randomUUID } from "node:crypto";
5
+ import {
6
+ existsSync,
7
+ mkdirSync,
8
+ readFileSync,
9
+ writeFileSync,
10
+ renameSync,
11
+ } from "node:fs";
12
+ import { join, dirname } from "node:path";
13
+ import { HEYHANK_HOME } from "./paths.js";
14
+
15
+ // ─── Types ───────────────────────────────────────────────────────────────────
16
+
17
+ export type MessageType =
18
+ | "task"
19
+ | "result"
20
+ | "question"
21
+ | "info"
22
+ | "status"
23
+ | "interrupt";
24
+
25
+ export const VALID_MESSAGE_TYPES: MessageType[] = [
26
+ "task",
27
+ "result",
28
+ "question",
29
+ "info",
30
+ "status",
31
+ "interrupt",
32
+ ];
33
+
34
+ export interface AgentMessage {
35
+ id: string;
36
+ from: string;
37
+ fromName?: string;
38
+ to?: string;
39
+ channel?: string;
40
+ type: MessageType;
41
+ content: string;
42
+ metadata?: Record<string, unknown>;
43
+ createdAt: string;
44
+ readBy: string[];
45
+ }
46
+
47
+ export interface MessageQuery {
48
+ to?: string;
49
+ from?: string;
50
+ channel?: string;
51
+ type?: MessageType;
52
+ unreadBy?: string;
53
+ since?: string;
54
+ limit?: number;
55
+ }
56
+
57
+ type MessageListener = (msg: AgentMessage) => void;
58
+
59
+ // ─── Constants ───────────────────────────────────────────────────────────────
60
+
61
+ const MAX_MESSAGES = 500;
62
+ const MESSAGES_FILE = join(HEYHANK_HOME, "messages.jsonl");
63
+ const DEBOUNCE_MS = 500;
64
+
65
+ // ─── MessageBus ──────────────────────────────────────────────────────────────
66
+
67
+ export class MessageBus {
68
+ private messages: AgentMessage[] = [];
69
+ private listeners = new Set<MessageListener>();
70
+ private persistTimer: ReturnType<typeof setTimeout> | null = null;
71
+ private dirty = false;
72
+
73
+ constructor() {
74
+ this.restore();
75
+ }
76
+
77
+ /** Post a new message to the bus. */
78
+ post(opts: {
79
+ from: string;
80
+ fromName?: string;
81
+ to?: string;
82
+ channel?: string;
83
+ type: MessageType;
84
+ content: string;
85
+ metadata?: Record<string, unknown>;
86
+ }): AgentMessage {
87
+ const msg: AgentMessage = {
88
+ id: randomUUID(),
89
+ from: opts.from,
90
+ fromName: opts.fromName,
91
+ to: opts.to,
92
+ channel: opts.channel,
93
+ type: opts.type,
94
+ content: opts.content,
95
+ metadata: opts.metadata,
96
+ createdAt: new Date().toISOString(),
97
+ readBy: [],
98
+ };
99
+
100
+ this.messages.push(msg);
101
+ this.trim();
102
+ this.schedulePersist();
103
+
104
+ // Notify listeners
105
+ for (const listener of this.listeners) {
106
+ try {
107
+ listener(msg);
108
+ } catch (err) {
109
+ console.error("[message-bus] Listener error:", err);
110
+ }
111
+ }
112
+
113
+ return msg;
114
+ }
115
+
116
+ /** Query messages with optional filters. */
117
+ query(opts?: MessageQuery): AgentMessage[] {
118
+ let result = [...this.messages];
119
+ if (opts?.to) {
120
+ result = result.filter(
121
+ (m) => m.to === opts.to || m.to === undefined,
122
+ );
123
+ }
124
+ if (opts?.from) {
125
+ result = result.filter((m) => m.from === opts.from);
126
+ }
127
+ if (opts?.channel) {
128
+ result = result.filter((m) => m.channel === opts.channel);
129
+ }
130
+ if (opts?.type) {
131
+ result = result.filter((m) => m.type === opts.type);
132
+ }
133
+ if (opts?.unreadBy) {
134
+ const agentId = opts.unreadBy;
135
+ result = result.filter((m) => !m.readBy.includes(agentId));
136
+ }
137
+ if (opts?.since) {
138
+ result = result.filter((m) => m.createdAt > opts.since!);
139
+ }
140
+ const limit = opts?.limit ?? 100;
141
+ return result.slice(-limit);
142
+ }
143
+
144
+ /** Mark a message as read by an agent. */
145
+ markRead(messageId: string, agentId: string): void {
146
+ const msg = this.messages.find((m) => m.id === messageId);
147
+ if (msg && !msg.readBy.includes(agentId)) {
148
+ msg.readBy.push(agentId);
149
+ this.schedulePersist();
150
+ }
151
+ }
152
+
153
+ /** Mark all messages targeted at an agent as read. */
154
+ markAllRead(agentId: string): void {
155
+ let changed = false;
156
+ for (const msg of this.messages) {
157
+ if (
158
+ (msg.to === agentId || msg.to === undefined) &&
159
+ !msg.readBy.includes(agentId)
160
+ ) {
161
+ msg.readBy.push(agentId);
162
+ changed = true;
163
+ }
164
+ }
165
+ if (changed) this.schedulePersist();
166
+ }
167
+
168
+ /** Get count of unread messages for an agent. */
169
+ unreadCount(agentId: string): number {
170
+ return this.messages.filter(
171
+ (m) =>
172
+ (m.to === agentId || m.to === undefined) &&
173
+ !m.readBy.includes(agentId),
174
+ ).length;
175
+ }
176
+
177
+ /** Delete a single message. */
178
+ deleteMessage(id: string): boolean {
179
+ const idx = this.messages.findIndex((m) => m.id === id);
180
+ if (idx === -1) return false;
181
+ this.messages.splice(idx, 1);
182
+ this.schedulePersist();
183
+ return true;
184
+ }
185
+
186
+ /** Clear all messages. */
187
+ clearAll(): void {
188
+ this.messages = [];
189
+ this.persistNow();
190
+ }
191
+
192
+ /** Remove all messages to/from a specific agent. */
193
+ cleanupForAgent(agentId: string): void {
194
+ const before = this.messages.length;
195
+ this.messages = this.messages.filter(
196
+ (m) => m.from !== agentId && m.to !== agentId,
197
+ );
198
+ if (this.messages.length !== before) {
199
+ this.schedulePersist();
200
+ }
201
+ }
202
+
203
+ /** Subscribe to new messages. Returns unsubscribe function. */
204
+ subscribe(listener: MessageListener): () => void {
205
+ this.listeners.add(listener);
206
+ return () => {
207
+ this.listeners.delete(listener);
208
+ };
209
+ }
210
+
211
+ /** Get all messages (for debugging/admin). */
212
+ getAll(): AgentMessage[] {
213
+ return [...this.messages];
214
+ }
215
+
216
+ // ─── Persistence ─────────────────────────────────────────────────────
217
+
218
+ private trim(): void {
219
+ if (this.messages.length > MAX_MESSAGES) {
220
+ this.messages = this.messages.slice(-MAX_MESSAGES);
221
+ }
222
+ }
223
+
224
+ private schedulePersist(): void {
225
+ this.dirty = true;
226
+ if (this.persistTimer) return;
227
+ this.persistTimer = setTimeout(() => {
228
+ this.persistTimer = null;
229
+ if (this.dirty) this.persistNow();
230
+ }, DEBOUNCE_MS);
231
+ }
232
+
233
+ private persistNow(): void {
234
+ this.dirty = false;
235
+ try {
236
+ mkdirSync(dirname(MESSAGES_FILE), { recursive: true });
237
+ const data = this.messages.map((m) => JSON.stringify(m)).join("\n") + "\n";
238
+ const tmpFile = MESSAGES_FILE + ".tmp";
239
+ writeFileSync(tmpFile, data, "utf-8");
240
+ renameSync(tmpFile, MESSAGES_FILE);
241
+ } catch (err) {
242
+ console.error("[message-bus] Failed to persist:", err);
243
+ }
244
+ }
245
+
246
+ private restore(): void {
247
+ try {
248
+ if (!existsSync(MESSAGES_FILE)) return;
249
+ const raw = readFileSync(MESSAGES_FILE, "utf-8");
250
+ const lines = raw.split("\n").filter((l) => l.trim());
251
+ const restored: AgentMessage[] = [];
252
+ for (const line of lines) {
253
+ try {
254
+ restored.push(JSON.parse(line));
255
+ } catch {
256
+ // Skip corrupt lines
257
+ }
258
+ }
259
+ // Keep only last MAX_MESSAGES
260
+ this.messages = restored.slice(-MAX_MESSAGES);
261
+ console.log(
262
+ `[message-bus] Restored ${this.messages.length} messages from disk`,
263
+ );
264
+ } catch (err) {
265
+ console.error("[message-bus] Failed to restore:", err);
266
+ }
267
+ }
268
+ }
269
+
270
+ /** Singleton instance */
271
+ export const messageBus = new MessageBus();
@@ -0,0 +1,128 @@
1
+ // ─── Message Delivery System ─────────────────────────────────────────────────
2
+ // Auto-delivers inter-agent messages to idle agents
3
+ // Ported from AgentManager/src/message-delivery.ts
4
+
5
+ import { messageBus } from "./message-bus.js";
6
+ import type { AgentMessage } from "./message-bus.js";
7
+ import type { AgentExecutor } from "./agent-executor.js";
8
+ import type { WsBridge } from "./ws-bridge.js";
9
+ import { isKilled } from "./kill-switch.js";
10
+
11
+ // ─── Types ───────────────────────────────────────────────────────────────────
12
+
13
+ export interface MessageDeliveryOptions {
14
+ /** Delay before delivering queued messages (ms) */
15
+ deliverySettleMs?: number;
16
+ }
17
+
18
+ // ─── Delivery Prompt Formatting ──────────────────────────────────────────────
19
+
20
+ export function formatDeliveryPrompt(
21
+ fromName: string,
22
+ fromId: string,
23
+ content: string,
24
+ type: string,
25
+ ): string {
26
+ if (type === "interrupt") {
27
+ return `[INTERRUPT from ${fromName}] ⚠️ Your current task has been interrupted. Read and act on this message immediately:
28
+
29
+ <message-content>
30
+ ${content}
31
+ </message-content>
32
+
33
+ (Reply by sending a message to agent ID: ${fromId})`;
34
+ }
35
+
36
+ return `[Message from ${fromName} (${fromId})] Type: ${type}
37
+
38
+ <message-content>
39
+ ${content}
40
+ </message-content>
41
+
42
+ Process this message and respond appropriately. To reply, send a message to agent ID: ${fromId}`;
43
+ }
44
+
45
+ // ─── Attach Auto-Delivery ────────────────────────────────────────────────────
46
+
47
+ /**
48
+ * Subscribe to the message bus and auto-deliver messages to target agent sessions.
49
+ * When a message arrives for a specific agent, injects it as a user message
50
+ * into that agent's active session.
51
+ */
52
+ export function attachMessageDelivery(
53
+ agentExecutor: AgentExecutor,
54
+ wsBridge: WsBridge,
55
+ opts?: MessageDeliveryOptions,
56
+ ): () => void {
57
+ const settleMs = opts?.deliverySettleMs ?? 2000;
58
+
59
+ const unsubscribe = messageBus.subscribe((msg: AgentMessage) => {
60
+ // Skip broadcasts (no specific target)
61
+ if (!msg.to) return;
62
+
63
+ // Skip status messages
64
+ if (msg.type === "status") return;
65
+
66
+ // Kill switch check
67
+ if (isKilled()) {
68
+ console.log(
69
+ `[message-delivery] Skipping delivery (kill switch active): ${msg.id}`,
70
+ );
71
+ return;
72
+ }
73
+
74
+ // Find the target agent's active session
75
+ const targetAgentId = msg.to;
76
+
77
+ // Delay delivery slightly to let agents settle
78
+ setTimeout(() => {
79
+ try {
80
+ deliverToAgent(targetAgentId, msg, wsBridge);
81
+ } catch (err) {
82
+ console.error(
83
+ `[message-delivery] Failed to deliver to ${targetAgentId}:`,
84
+ err,
85
+ );
86
+ }
87
+ }, settleMs);
88
+ });
89
+
90
+ console.log("[message-delivery] Auto-delivery attached");
91
+ return unsubscribe;
92
+ }
93
+
94
+ function deliverToAgent(
95
+ agentId: string,
96
+ msg: AgentMessage,
97
+ wsBridge: WsBridge,
98
+ ): void {
99
+ // Find active sessions for this agent
100
+ const sessions = wsBridge.getSessionMemoryStats();
101
+
102
+ // Look for a session that belongs to this agent
103
+ // Agent sessions are tagged with agentId in their metadata
104
+ for (const session of sessions) {
105
+ // Try to inject the message
106
+ const prompt = formatDeliveryPrompt(
107
+ msg.fromName || msg.from,
108
+ msg.from,
109
+ msg.content,
110
+ msg.type,
111
+ );
112
+
113
+ try {
114
+ wsBridge.injectUserMessage(session.id, prompt);
115
+ messageBus.markRead(msg.id, agentId);
116
+ console.log(
117
+ `[message-delivery] Delivered ${msg.type} from ${msg.from} to ${agentId} (session ${session.id.slice(0, 8)})`,
118
+ );
119
+ return;
120
+ } catch {
121
+ // Session might not accept messages, try next
122
+ }
123
+ }
124
+
125
+ console.log(
126
+ `[message-delivery] No active session for ${agentId}, message ${msg.id} queued`,
127
+ );
128
+ }