@cleocode/runtime 2026.4.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 CLEO Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,256 @@
1
+ import * as _cleocode_contracts from '@cleocode/contracts';
2
+ import { Transport, ConduitMessage, AgentRegistryAPI } from '@cleocode/contracts';
3
+
4
+ /**
5
+ * AgentPoller — Polls for messages via HttpTransport AND group conversations.
6
+ *
7
+ * Fixes the group @mention blind spot: the peek endpoint only matches
8
+ * to_agent_id (DMs). This poller ALSO checks known group conversation
9
+ * messages for @agentId content matches.
10
+ *
11
+ * @task T183
12
+ */
13
+
14
+ /** Message handler callback. */
15
+ type MessageHandler = (message: ConduitMessage) => void;
16
+ /** Poller configuration. */
17
+ interface AgentPollerConfig {
18
+ /** Agent ID to poll as. */
19
+ agentId: string;
20
+ /** API key for authentication. */
21
+ apiKey: string;
22
+ /** API base URL. */
23
+ apiBaseUrl: string;
24
+ /** Poll interval in milliseconds. Default: 5000. */
25
+ pollIntervalMs?: number;
26
+ /** Known group conversation IDs to monitor for @mentions. */
27
+ groupConversationIds?: string[];
28
+ /** Max messages to fetch per group conversation poll. Default: 15. */
29
+ groupPollLimit?: number;
30
+ /**
31
+ * Transport instance for polling messages.
32
+ * When provided, poll() delegates to transport.poll() instead of raw HTTP.
33
+ * The transport must already be connected before passing to AgentPoller.
34
+ */
35
+ transport?: Transport;
36
+ }
37
+ /**
38
+ * AgentPoller service — polls peek endpoint AND group conversations.
39
+ * Deduplicates messages by ID across both sources.
40
+ */
41
+ declare class AgentPoller {
42
+ private config;
43
+ private handler;
44
+ private interval;
45
+ private seenMessageIds;
46
+ private running;
47
+ constructor(config: AgentPollerConfig);
48
+ /** Register a message handler. */
49
+ onMessage(handler: MessageHandler): void;
50
+ /** Start the polling loop. */
51
+ start(): void;
52
+ /** Stop the polling loop. */
53
+ stop(): void;
54
+ /** Get poller status. */
55
+ status(): {
56
+ running: boolean;
57
+ seenCount: number;
58
+ };
59
+ /** Single poll cycle — peek + group conversations. */
60
+ private pollCycle;
61
+ /** Peek for messages mentioning this agent. Delegates to transport when available. */
62
+ private peekMessages;
63
+ /**
64
+ * Poll a group conversation for recent messages that @mention this agent.
65
+ * This is the fix for the group @mention blind spot.
66
+ */
67
+ private pollGroupConversation;
68
+ /** Build auth headers. */
69
+ private headers;
70
+ }
71
+
72
+ /**
73
+ * HeartbeatService — Periodic online status heartbeat.
74
+ *
75
+ * Sends a heartbeat to the SignalDock API at a configurable interval
76
+ * to maintain the agent's online status. If the heartbeat fails,
77
+ * it retries silently — the agent continues operating regardless.
78
+ *
79
+ * @task T218
80
+ */
81
+ /** Heartbeat service configuration. */
82
+ interface HeartbeatConfig {
83
+ /** Agent ID to send heartbeats for. */
84
+ agentId: string;
85
+ /** API key for authentication. */
86
+ apiKey: string;
87
+ /** API base URL. */
88
+ apiBaseUrl: string;
89
+ /** Heartbeat interval in milliseconds. Default: 30000 (30s). */
90
+ intervalMs?: number;
91
+ }
92
+ /** HeartbeatService sends periodic online status to the cloud API. */
93
+ declare class HeartbeatService {
94
+ private config;
95
+ private timer;
96
+ private running;
97
+ private consecutiveFailures;
98
+ constructor(config: HeartbeatConfig);
99
+ /** Start sending heartbeats at the configured interval. */
100
+ start(): void;
101
+ /** Stop sending heartbeats. */
102
+ stop(): void;
103
+ /** Get heartbeat service status. */
104
+ status(): {
105
+ running: boolean;
106
+ consecutiveFailures: number;
107
+ };
108
+ /** Send a single heartbeat to the cloud API. */
109
+ private sendHeartbeat;
110
+ }
111
+
112
+ /**
113
+ * KeyRotationService — Automatic API key rotation based on credential age.
114
+ *
115
+ * Monitors the age of the agent's API key and triggers rotation when
116
+ * the key exceeds the configured threshold. Uses the AgentRegistryAPI
117
+ * to perform the actual rotation (which calls the cloud API and
118
+ * re-encrypts the new key locally).
119
+ *
120
+ * @task T218
121
+ */
122
+
123
+ /** Key rotation service configuration. */
124
+ interface KeyRotationConfig {
125
+ /** Agent ID to monitor. */
126
+ agentId: string;
127
+ /** AgentRegistryAPI instance for credential lookup and rotation. */
128
+ registry: AgentRegistryAPI;
129
+ /** Check interval in milliseconds. Default: 3600000 (1 hour). */
130
+ checkIntervalMs?: number;
131
+ /** Max key age in milliseconds before rotation. Default: 2592000000 (30 days). */
132
+ maxKeyAgeMs?: number;
133
+ }
134
+ /** KeyRotationService monitors credential age and auto-rotates when threshold is exceeded. */
135
+ declare class KeyRotationService {
136
+ private config;
137
+ private timer;
138
+ private running;
139
+ private lastRotationAt;
140
+ constructor(config: KeyRotationConfig);
141
+ /** Start monitoring key age at the configured interval. */
142
+ start(): void;
143
+ /** Stop monitoring. */
144
+ stop(): void;
145
+ /** Get rotation service status. */
146
+ status(): {
147
+ running: boolean;
148
+ lastRotationAt: string | null;
149
+ };
150
+ /** Check credential age and rotate if needed. */
151
+ private checkAndRotate;
152
+ }
153
+
154
+ /**
155
+ * SseConnectionService — Persistent SSE connection manager.
156
+ *
157
+ * Maintains a persistent SSE connection to the SignalDock API for
158
+ * real-time message delivery. Wraps SseTransport with lifecycle
159
+ * management: start, stop, reconnect, and message forwarding.
160
+ *
161
+ * When SSE is available, messages arrive in real-time. When it falls
162
+ * back to HTTP polling (managed by SseTransport internally), the
163
+ * service continues operating transparently.
164
+ *
165
+ * @task T218
166
+ */
167
+
168
+ /** SSE connection service configuration. */
169
+ interface SseConnectionConfig {
170
+ /** Agent ID to connect as. */
171
+ agentId: string;
172
+ /** API key for authentication. */
173
+ apiKey: string;
174
+ /** API base URL. */
175
+ apiBaseUrl: string;
176
+ /** SSE endpoint URL. If omitted, uses apiBaseUrl + /sse. */
177
+ sseEndpoint?: string;
178
+ /** Transport instance to use. Injected by createRuntime. */
179
+ transport: Transport;
180
+ }
181
+ /** Message handler callback. */
182
+ type SseMessageHandler = (message: ConduitMessage) => void;
183
+ /** SseConnectionService manages a persistent transport with subscribe() support. */
184
+ declare class SseConnectionService {
185
+ private config;
186
+ private handler;
187
+ private unsubscribe;
188
+ private running;
189
+ constructor(config: SseConnectionConfig);
190
+ /** Register a message handler for incoming messages. */
191
+ onMessage(handler: SseMessageHandler): void;
192
+ /** Start the SSE connection. */
193
+ start(): Promise<void>;
194
+ /** Stop the connection and clean up. */
195
+ stop(): Promise<void>;
196
+ /** Get connection service status. */
197
+ status(): {
198
+ running: boolean;
199
+ transportName: string;
200
+ };
201
+ }
202
+
203
+ /** Configuration for createRuntime(). */
204
+ interface RuntimeConfig {
205
+ /** Agent ID to run as. If omitted, uses the most recently used active agent. */
206
+ agentId?: string;
207
+ /** Poll interval in milliseconds. Default: 5000. */
208
+ pollIntervalMs?: number;
209
+ /** Known group conversation IDs to monitor for @mentions. */
210
+ groupConversationIds?: string[];
211
+ /** Max messages per group conversation poll. Default: 15. */
212
+ groupPollLimit?: number;
213
+ /** Heartbeat interval in milliseconds. Default: 30000. Set to 0 to disable. */
214
+ heartbeatIntervalMs?: number;
215
+ /** Max key age in milliseconds before rotation. Default: 30 days. Set to 0 to disable. */
216
+ maxKeyAgeMs?: number;
217
+ /** SSE endpoint URL. If set, enables persistent SSE connection. */
218
+ sseEndpoint?: string;
219
+ /** Transport factory for SSE connection. Caller provides to avoid circular deps. */
220
+ createSseTransport?: () => _cleocode_contracts.Transport;
221
+ /**
222
+ * Pre-created transport instance. When provided, bypasses auto-resolution.
223
+ * The transport must NOT be connected yet — createRuntime handles connection.
224
+ */
225
+ transport?: Transport;
226
+ }
227
+ /** Handle returned by createRuntime(). */
228
+ interface RuntimeHandle {
229
+ /** The AgentPoller instance. */
230
+ poller: AgentPoller;
231
+ /** The HeartbeatService instance (null if disabled). */
232
+ heartbeat: HeartbeatService | null;
233
+ /** The KeyRotationService instance (null if disabled). */
234
+ keyRotation: KeyRotationService | null;
235
+ /** The SseConnectionService instance (null if no SSE endpoint). */
236
+ sseConnection: SseConnectionService | null;
237
+ /** The resolved transport (local, sse, or http). */
238
+ transport: Transport;
239
+ /** The agent ID the runtime is running as. */
240
+ agentId: string;
241
+ /** Stop all runtime services and clean up. */
242
+ stop: () => void;
243
+ }
244
+ /**
245
+ * Create and start a runtime from the agent registry.
246
+ *
247
+ * Resolves the agent credential, configures the poller, and starts polling.
248
+ * Returns a handle to register message handlers and stop the runtime.
249
+ *
250
+ * @param registry - AgentRegistryAPI instance for credential lookup.
251
+ * @param config - Optional runtime configuration overrides.
252
+ * @returns A RuntimeHandle with the poller, agentId, and stop function.
253
+ */
254
+ declare function createRuntime(registry: AgentRegistryAPI, config?: RuntimeConfig): Promise<RuntimeHandle>;
255
+
256
+ export { AgentPoller, type AgentPollerConfig, type HeartbeatConfig, HeartbeatService, type KeyRotationConfig, KeyRotationService, type MessageHandler, type RuntimeConfig, type RuntimeHandle, type SseConnectionConfig, SseConnectionService, type SseMessageHandler, createRuntime };
package/dist/index.js ADDED
@@ -0,0 +1,380 @@
1
+ // src/index.ts
2
+ import { conduit } from "@cleocode/core";
3
+
4
+ // src/services/agent-poller.ts
5
+ var DEFAULT_POLL_INTERVAL = 5e3;
6
+ var DEFAULT_GROUP_POLL_LIMIT = 15;
7
+ var AgentPoller = class {
8
+ config;
9
+ handler = null;
10
+ interval = null;
11
+ seenMessageIds = /* @__PURE__ */ new Set();
12
+ running = false;
13
+ constructor(config) {
14
+ this.config = config;
15
+ }
16
+ /** Register a message handler. */
17
+ onMessage(handler) {
18
+ this.handler = handler;
19
+ }
20
+ /** Start the polling loop. */
21
+ start() {
22
+ if (this.running) return;
23
+ this.running = true;
24
+ const intervalMs = this.config.pollIntervalMs ?? DEFAULT_POLL_INTERVAL;
25
+ void this.pollCycle();
26
+ this.interval = setInterval(() => {
27
+ void this.pollCycle();
28
+ }, intervalMs);
29
+ }
30
+ /** Stop the polling loop. */
31
+ stop() {
32
+ this.running = false;
33
+ if (this.interval) {
34
+ clearInterval(this.interval);
35
+ this.interval = null;
36
+ }
37
+ }
38
+ /** Get poller status. */
39
+ status() {
40
+ return {
41
+ running: this.running,
42
+ seenCount: this.seenMessageIds.size
43
+ };
44
+ }
45
+ /** Single poll cycle — peek + group conversations. */
46
+ async pollCycle() {
47
+ if (!this.handler) return;
48
+ const newMessages = [];
49
+ try {
50
+ const peekMessages = await this.peekMessages();
51
+ for (const msg of peekMessages) {
52
+ if (!this.seenMessageIds.has(msg.id)) {
53
+ this.seenMessageIds.add(msg.id);
54
+ newMessages.push(msg);
55
+ }
56
+ }
57
+ } catch {
58
+ }
59
+ const groupIds = this.config.groupConversationIds ?? [];
60
+ for (const convId of groupIds) {
61
+ try {
62
+ const groupMessages = await this.pollGroupConversation(convId);
63
+ for (const msg of groupMessages) {
64
+ if (!this.seenMessageIds.has(msg.id)) {
65
+ this.seenMessageIds.add(msg.id);
66
+ newMessages.push(msg);
67
+ }
68
+ }
69
+ } catch {
70
+ }
71
+ }
72
+ for (const msg of newMessages) {
73
+ this.handler(msg);
74
+ }
75
+ if (this.seenMessageIds.size > 5e3) {
76
+ const entries = [...this.seenMessageIds];
77
+ this.seenMessageIds = new Set(entries.slice(-3e3));
78
+ }
79
+ }
80
+ /** Peek for messages mentioning this agent. Delegates to transport when available. */
81
+ async peekMessages() {
82
+ if (this.config.transport) {
83
+ return this.config.transport.poll({ limit: 50 });
84
+ }
85
+ const params = new URLSearchParams();
86
+ params.set("mentioned", this.config.agentId);
87
+ params.set("limit", "50");
88
+ const url = `${this.config.apiBaseUrl}/messages/peek?${params}`;
89
+ const response = await fetch(url, {
90
+ method: "GET",
91
+ headers: this.headers()
92
+ });
93
+ if (!response.ok) return [];
94
+ const data = await response.json();
95
+ return (data.data?.messages ?? []).map((m) => ({
96
+ id: m.id,
97
+ from: m.fromAgentId ?? "unknown",
98
+ content: m.content ?? "",
99
+ threadId: m.conversationId,
100
+ timestamp: m.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
101
+ }));
102
+ }
103
+ /**
104
+ * Poll a group conversation for recent messages that @mention this agent.
105
+ * This is the fix for the group @mention blind spot.
106
+ */
107
+ async pollGroupConversation(conversationId) {
108
+ const limit = this.config.groupPollLimit ?? DEFAULT_GROUP_POLL_LIMIT;
109
+ const url = `${this.config.apiBaseUrl}/conversations/${conversationId}/messages?sort=desc&limit=${limit}`;
110
+ const response = await fetch(url, {
111
+ method: "GET",
112
+ headers: this.headers()
113
+ });
114
+ if (!response.ok) return [];
115
+ const data = await response.json();
116
+ const mentionPattern = new RegExp(`@${this.config.agentId}\\b|@all\\b`, "i");
117
+ return (data.data?.messages ?? []).filter((m) => {
118
+ const content = m.content ?? "";
119
+ return mentionPattern.test(content) && m.fromAgentId !== this.config.agentId;
120
+ }).map((m) => ({
121
+ id: m.id,
122
+ from: m.fromAgentId ?? "unknown",
123
+ content: m.content ?? "",
124
+ threadId: m.conversationId ?? conversationId,
125
+ timestamp: m.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
126
+ }));
127
+ }
128
+ /** Build auth headers. */
129
+ headers() {
130
+ return {
131
+ "Content-Type": "application/json",
132
+ Authorization: `Bearer ${this.config.apiKey}`,
133
+ "X-Agent-Id": this.config.agentId
134
+ };
135
+ }
136
+ };
137
+
138
+ // src/services/heartbeat.ts
139
+ var DEFAULT_INTERVAL_MS = 3e4;
140
+ var HeartbeatService = class {
141
+ config;
142
+ timer = null;
143
+ running = false;
144
+ consecutiveFailures = 0;
145
+ constructor(config) {
146
+ this.config = config;
147
+ }
148
+ /** Start sending heartbeats at the configured interval. */
149
+ start() {
150
+ if (this.running) return;
151
+ this.running = true;
152
+ this.consecutiveFailures = 0;
153
+ const intervalMs = this.config.intervalMs ?? DEFAULT_INTERVAL_MS;
154
+ void this.sendHeartbeat();
155
+ this.timer = setInterval(() => {
156
+ void this.sendHeartbeat();
157
+ }, intervalMs);
158
+ }
159
+ /** Stop sending heartbeats. */
160
+ stop() {
161
+ this.running = false;
162
+ if (this.timer) {
163
+ clearInterval(this.timer);
164
+ this.timer = null;
165
+ }
166
+ }
167
+ /** Get heartbeat service status. */
168
+ status() {
169
+ return {
170
+ running: this.running,
171
+ consecutiveFailures: this.consecutiveFailures
172
+ };
173
+ }
174
+ /** Send a single heartbeat to the cloud API. */
175
+ async sendHeartbeat() {
176
+ try {
177
+ const response = await fetch(
178
+ `${this.config.apiBaseUrl}/agents/${this.config.agentId}/heartbeat`,
179
+ {
180
+ method: "POST",
181
+ headers: {
182
+ "Content-Type": "application/json",
183
+ Authorization: `Bearer ${this.config.apiKey}`,
184
+ "X-Agent-Id": this.config.agentId
185
+ },
186
+ body: JSON.stringify({ status: "online" }),
187
+ signal: AbortSignal.timeout(1e4)
188
+ }
189
+ );
190
+ if (response.ok) {
191
+ this.consecutiveFailures = 0;
192
+ } else {
193
+ this.consecutiveFailures++;
194
+ }
195
+ } catch {
196
+ this.consecutiveFailures++;
197
+ }
198
+ }
199
+ };
200
+
201
+ // src/services/key-rotation.ts
202
+ var DEFAULT_CHECK_INTERVAL_MS = 36e5;
203
+ var DEFAULT_MAX_KEY_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
204
+ var KeyRotationService = class {
205
+ config;
206
+ timer = null;
207
+ running = false;
208
+ lastRotationAt = null;
209
+ constructor(config) {
210
+ this.config = config;
211
+ }
212
+ /** Start monitoring key age at the configured interval. */
213
+ start() {
214
+ if (this.running) return;
215
+ this.running = true;
216
+ const intervalMs = this.config.checkIntervalMs ?? DEFAULT_CHECK_INTERVAL_MS;
217
+ setTimeout(() => {
218
+ void this.checkAndRotate();
219
+ }, 5e3);
220
+ this.timer = setInterval(() => {
221
+ void this.checkAndRotate();
222
+ }, intervalMs);
223
+ }
224
+ /** Stop monitoring. */
225
+ stop() {
226
+ this.running = false;
227
+ if (this.timer) {
228
+ clearInterval(this.timer);
229
+ this.timer = null;
230
+ }
231
+ }
232
+ /** Get rotation service status. */
233
+ status() {
234
+ return {
235
+ running: this.running,
236
+ lastRotationAt: this.lastRotationAt
237
+ };
238
+ }
239
+ /** Check credential age and rotate if needed. */
240
+ async checkAndRotate() {
241
+ try {
242
+ const credential = await this.config.registry.get(this.config.agentId);
243
+ if (!credential) return;
244
+ const maxAge = this.config.maxKeyAgeMs ?? DEFAULT_MAX_KEY_AGE_MS;
245
+ const credentialAge = Date.now() - new Date(credential.updatedAt).getTime();
246
+ if (credentialAge > maxAge) {
247
+ await this.config.registry.rotateKey(this.config.agentId);
248
+ this.lastRotationAt = (/* @__PURE__ */ new Date()).toISOString();
249
+ }
250
+ } catch {
251
+ }
252
+ }
253
+ };
254
+
255
+ // src/services/sse-connection.ts
256
+ var SseConnectionService = class {
257
+ config;
258
+ handler = null;
259
+ unsubscribe = null;
260
+ running = false;
261
+ constructor(config) {
262
+ this.config = config;
263
+ }
264
+ /** Register a message handler for incoming messages. */
265
+ onMessage(handler) {
266
+ this.handler = handler;
267
+ }
268
+ /** Start the SSE connection. */
269
+ async start() {
270
+ if (this.running) return;
271
+ this.running = true;
272
+ await this.config.transport.connect({
273
+ agentId: this.config.agentId,
274
+ apiKey: this.config.apiKey,
275
+ apiBaseUrl: this.config.apiBaseUrl,
276
+ sseEndpoint: this.config.sseEndpoint
277
+ });
278
+ if (this.handler && this.config.transport.subscribe) {
279
+ this.unsubscribe = this.config.transport.subscribe(this.handler);
280
+ }
281
+ }
282
+ /** Stop the connection and clean up. */
283
+ async stop() {
284
+ this.running = false;
285
+ if (this.unsubscribe) {
286
+ this.unsubscribe();
287
+ this.unsubscribe = null;
288
+ }
289
+ await this.config.transport.disconnect();
290
+ }
291
+ /** Get connection service status. */
292
+ status() {
293
+ return {
294
+ running: this.running,
295
+ transportName: this.config.transport.name
296
+ };
297
+ }
298
+ };
299
+
300
+ // src/index.ts
301
+ var { resolveTransport } = conduit;
302
+ async function createRuntime(registry, config) {
303
+ const credential = config?.agentId ? await registry.get(config.agentId) : await registry.getActive();
304
+ if (!credential) {
305
+ throw new Error(
306
+ "No agent credential found. Run: cleo agent register --id <id> --api-key <key>"
307
+ );
308
+ }
309
+ const transport = config?.transport ?? resolveTransport(credential);
310
+ await transport.connect({
311
+ agentId: credential.agentId,
312
+ apiKey: credential.apiKey,
313
+ apiBaseUrl: credential.apiBaseUrl,
314
+ ...credential.transportConfig
315
+ });
316
+ const pollerConfig = {
317
+ agentId: credential.agentId,
318
+ apiKey: credential.apiKey,
319
+ apiBaseUrl: credential.apiBaseUrl,
320
+ pollIntervalMs: config?.pollIntervalMs ?? credential.transportConfig.pollIntervalMs ?? 5e3,
321
+ groupConversationIds: config?.groupConversationIds,
322
+ groupPollLimit: config?.groupPollLimit,
323
+ transport
324
+ };
325
+ const poller = new AgentPoller(pollerConfig);
326
+ let heartbeat = null;
327
+ if (config?.heartbeatIntervalMs !== 0) {
328
+ heartbeat = new HeartbeatService({
329
+ agentId: credential.agentId,
330
+ apiKey: credential.apiKey,
331
+ apiBaseUrl: credential.apiBaseUrl,
332
+ intervalMs: config?.heartbeatIntervalMs
333
+ });
334
+ heartbeat.start();
335
+ }
336
+ let keyRotation = null;
337
+ if (config?.maxKeyAgeMs !== 0) {
338
+ keyRotation = new KeyRotationService({
339
+ agentId: credential.agentId,
340
+ registry,
341
+ maxKeyAgeMs: config?.maxKeyAgeMs
342
+ });
343
+ keyRotation.start();
344
+ }
345
+ let sseConnection = null;
346
+ const sseEndpoint = config?.sseEndpoint ?? credential.transportConfig.sseEndpoint;
347
+ if (sseEndpoint && config?.createSseTransport) {
348
+ sseConnection = new SseConnectionService({
349
+ agentId: credential.agentId,
350
+ apiKey: credential.apiKey,
351
+ apiBaseUrl: credential.apiBaseUrl,
352
+ sseEndpoint,
353
+ transport: config.createSseTransport()
354
+ });
355
+ void sseConnection.start();
356
+ }
357
+ return {
358
+ poller,
359
+ heartbeat,
360
+ keyRotation,
361
+ sseConnection,
362
+ transport,
363
+ agentId: credential.agentId,
364
+ stop: () => {
365
+ poller.stop();
366
+ heartbeat?.stop();
367
+ keyRotation?.stop();
368
+ void sseConnection?.stop();
369
+ void transport.disconnect();
370
+ }
371
+ };
372
+ }
373
+ export {
374
+ AgentPoller,
375
+ HeartbeatService,
376
+ KeyRotationService,
377
+ SseConnectionService,
378
+ createRuntime
379
+ };
380
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/services/agent-poller.ts","../src/services/heartbeat.ts","../src/services/key-rotation.ts","../src/services/sse-connection.ts"],"sourcesContent":["/**\n * @cleocode/runtime — Long-running process layer for CLEO.\n *\n * Provides background services: agent polling, SSE connections,\n * heartbeat intervals, and credential rotation.\n *\n * @module runtime\n */\n\nimport type { AgentRegistryAPI, Transport } from '@cleocode/contracts';\nimport { conduit } from '@cleocode/core';\n\nconst { resolveTransport } = conduit;\nimport type { AgentPollerConfig } from './services/agent-poller.js';\nimport { AgentPoller } from './services/agent-poller.js';\nimport { HeartbeatService } from './services/heartbeat.js';\nimport { KeyRotationService } from './services/key-rotation.js';\nimport { SseConnectionService } from './services/sse-connection.js';\n\nexport type { AgentPollerConfig, MessageHandler } from './services/agent-poller.js';\nexport { AgentPoller } from './services/agent-poller.js';\nexport type { HeartbeatConfig } from './services/heartbeat.js';\nexport { HeartbeatService } from './services/heartbeat.js';\nexport type { KeyRotationConfig } from './services/key-rotation.js';\nexport { KeyRotationService } from './services/key-rotation.js';\nexport type { SseConnectionConfig, SseMessageHandler } from './services/sse-connection.js';\nexport { SseConnectionService } from './services/sse-connection.js';\n\n/** Configuration for createRuntime(). */\nexport interface RuntimeConfig {\n /** Agent ID to run as. If omitted, uses the most recently used active agent. */\n agentId?: string;\n /** Poll interval in milliseconds. Default: 5000. */\n pollIntervalMs?: number;\n /** Known group conversation IDs to monitor for @mentions. */\n groupConversationIds?: string[];\n /** Max messages per group conversation poll. Default: 15. */\n groupPollLimit?: number;\n /** Heartbeat interval in milliseconds. Default: 30000. Set to 0 to disable. */\n heartbeatIntervalMs?: number;\n /** Max key age in milliseconds before rotation. Default: 30 days. Set to 0 to disable. */\n maxKeyAgeMs?: number;\n /** SSE endpoint URL. If set, enables persistent SSE connection. */\n sseEndpoint?: string;\n /** Transport factory for SSE connection. Caller provides to avoid circular deps. */\n createSseTransport?: () => import('@cleocode/contracts').Transport;\n /**\n * Pre-created transport instance. When provided, bypasses auto-resolution.\n * The transport must NOT be connected yet — createRuntime handles connection.\n */\n transport?: Transport;\n}\n\n/** Handle returned by createRuntime(). */\nexport interface RuntimeHandle {\n /** The AgentPoller instance. */\n poller: AgentPoller;\n /** The HeartbeatService instance (null if disabled). */\n heartbeat: HeartbeatService | null;\n /** The KeyRotationService instance (null if disabled). */\n keyRotation: KeyRotationService | null;\n /** The SseConnectionService instance (null if no SSE endpoint). */\n sseConnection: SseConnectionService | null;\n /** The resolved transport (local, sse, or http). */\n transport: Transport;\n /** The agent ID the runtime is running as. */\n agentId: string;\n /** Stop all runtime services and clean up. */\n stop: () => void;\n}\n\n/**\n * Create and start a runtime from the agent registry.\n *\n * Resolves the agent credential, configures the poller, and starts polling.\n * Returns a handle to register message handlers and stop the runtime.\n *\n * @param registry - AgentRegistryAPI instance for credential lookup.\n * @param config - Optional runtime configuration overrides.\n * @returns A RuntimeHandle with the poller, agentId, and stop function.\n */\nexport async function createRuntime(\n registry: AgentRegistryAPI,\n config?: RuntimeConfig,\n): Promise<RuntimeHandle> {\n const credential = config?.agentId\n ? await registry.get(config.agentId)\n : await registry.getActive();\n\n if (!credential) {\n throw new Error(\n 'No agent credential found. Run: cleo agent register --id <id> --api-key <key>',\n );\n }\n\n // Resolve transport: caller-provided > auto-detected (Local > SSE > HTTP)\n const transport = config?.transport ?? resolveTransport(credential);\n await transport.connect({\n agentId: credential.agentId,\n apiKey: credential.apiKey,\n apiBaseUrl: credential.apiBaseUrl,\n ...credential.transportConfig,\n });\n\n const pollerConfig: AgentPollerConfig = {\n agentId: credential.agentId,\n apiKey: credential.apiKey,\n apiBaseUrl: credential.apiBaseUrl,\n pollIntervalMs: config?.pollIntervalMs ?? credential.transportConfig.pollIntervalMs ?? 5000,\n groupConversationIds: config?.groupConversationIds,\n groupPollLimit: config?.groupPollLimit,\n transport,\n };\n\n const poller = new AgentPoller(pollerConfig);\n\n // Heartbeat service (disabled when intervalMs is 0)\n let heartbeat: HeartbeatService | null = null;\n if (config?.heartbeatIntervalMs !== 0) {\n heartbeat = new HeartbeatService({\n agentId: credential.agentId,\n apiKey: credential.apiKey,\n apiBaseUrl: credential.apiBaseUrl,\n intervalMs: config?.heartbeatIntervalMs,\n });\n heartbeat.start();\n }\n\n // Key rotation service (disabled when maxKeyAgeMs is 0)\n let keyRotation: KeyRotationService | null = null;\n if (config?.maxKeyAgeMs !== 0) {\n keyRotation = new KeyRotationService({\n agentId: credential.agentId,\n registry,\n maxKeyAgeMs: config?.maxKeyAgeMs,\n });\n keyRotation.start();\n }\n\n // SSE connection service (enabled when sseEndpoint + transport factory provided)\n let sseConnection: SseConnectionService | null = null;\n const sseEndpoint = config?.sseEndpoint ?? credential.transportConfig.sseEndpoint;\n if (sseEndpoint && config?.createSseTransport) {\n sseConnection = new SseConnectionService({\n agentId: credential.agentId,\n apiKey: credential.apiKey,\n apiBaseUrl: credential.apiBaseUrl,\n sseEndpoint,\n transport: config.createSseTransport(),\n });\n // Start is async but we don't block createRuntime on it\n void sseConnection.start();\n }\n\n return {\n poller,\n heartbeat,\n keyRotation,\n sseConnection,\n transport,\n agentId: credential.agentId,\n stop: () => {\n poller.stop();\n heartbeat?.stop();\n keyRotation?.stop();\n void sseConnection?.stop();\n void transport.disconnect();\n },\n };\n}\n","/**\n * AgentPoller — Polls for messages via HttpTransport AND group conversations.\n *\n * Fixes the group @mention blind spot: the peek endpoint only matches\n * to_agent_id (DMs). This poller ALSO checks known group conversation\n * messages for @agentId content matches.\n *\n * @task T183\n */\n\nimport type { ConduitMessage, Transport } from '@cleocode/contracts';\n\n/** Message handler callback. */\nexport type MessageHandler = (message: ConduitMessage) => void;\n\n/** Poller configuration. */\nexport interface AgentPollerConfig {\n /** Agent ID to poll as. */\n agentId: string;\n /** API key for authentication. */\n apiKey: string;\n /** API base URL. */\n apiBaseUrl: string;\n /** Poll interval in milliseconds. Default: 5000. */\n pollIntervalMs?: number;\n /** Known group conversation IDs to monitor for @mentions. */\n groupConversationIds?: string[];\n /** Max messages to fetch per group conversation poll. Default: 15. */\n groupPollLimit?: number;\n /**\n * Transport instance for polling messages.\n * When provided, poll() delegates to transport.poll() instead of raw HTTP.\n * The transport must already be connected before passing to AgentPoller.\n */\n transport?: Transport;\n}\n\n/** Tracks seen message IDs for dedup. */\nconst DEFAULT_POLL_INTERVAL = 5000;\nconst DEFAULT_GROUP_POLL_LIMIT = 15;\n\n/**\n * AgentPoller service — polls peek endpoint AND group conversations.\n * Deduplicates messages by ID across both sources.\n */\nexport class AgentPoller {\n private config: AgentPollerConfig;\n private handler: MessageHandler | null = null;\n private interval: ReturnType<typeof setInterval> | null = null;\n private seenMessageIds = new Set<string>();\n private running = false;\n\n constructor(config: AgentPollerConfig) {\n this.config = config;\n }\n\n /** Register a message handler. */\n onMessage(handler: MessageHandler): void {\n this.handler = handler;\n }\n\n /** Start the polling loop. */\n start(): void {\n if (this.running) return;\n this.running = true;\n\n const intervalMs = this.config.pollIntervalMs ?? DEFAULT_POLL_INTERVAL;\n\n // Initial poll immediately\n void this.pollCycle();\n\n this.interval = setInterval(() => {\n void this.pollCycle();\n }, intervalMs);\n }\n\n /** Stop the polling loop. */\n stop(): void {\n this.running = false;\n if (this.interval) {\n clearInterval(this.interval);\n this.interval = null;\n }\n }\n\n /** Get poller status. */\n status(): { running: boolean; seenCount: number } {\n return {\n running: this.running,\n seenCount: this.seenMessageIds.size,\n };\n }\n\n /** Single poll cycle — peek + group conversations. */\n private async pollCycle(): Promise<void> {\n if (!this.handler) return;\n\n const newMessages: ConduitMessage[] = [];\n\n // Track 1: Standard peek endpoint (catches DMs)\n try {\n const peekMessages = await this.peekMessages();\n for (const msg of peekMessages) {\n if (!this.seenMessageIds.has(msg.id)) {\n this.seenMessageIds.add(msg.id);\n newMessages.push(msg);\n }\n }\n } catch {\n // Best-effort — don't crash the loop\n }\n\n // Track 2: Group conversation polling (catches @mentions in group messages)\n const groupIds = this.config.groupConversationIds ?? [];\n for (const convId of groupIds) {\n try {\n const groupMessages = await this.pollGroupConversation(convId);\n for (const msg of groupMessages) {\n if (!this.seenMessageIds.has(msg.id)) {\n this.seenMessageIds.add(msg.id);\n newMessages.push(msg);\n }\n }\n } catch {\n // Best-effort per conversation\n }\n }\n\n // Deliver new messages to handler\n for (const msg of newMessages) {\n this.handler(msg);\n }\n\n // Prevent unbounded growth of seen set (keep last 5000)\n if (this.seenMessageIds.size > 5000) {\n const entries = [...this.seenMessageIds];\n this.seenMessageIds = new Set(entries.slice(-3000));\n }\n }\n\n /** Peek for messages mentioning this agent. Delegates to transport when available. */\n private async peekMessages(): Promise<ConduitMessage[]> {\n if (this.config.transport) {\n return this.config.transport.poll({ limit: 50 });\n }\n\n // Fallback: raw HTTP when no transport injected\n const params = new URLSearchParams();\n params.set('mentioned', this.config.agentId);\n params.set('limit', '50');\n\n const url = `${this.config.apiBaseUrl}/messages/peek?${params}`;\n const response = await fetch(url, {\n method: 'GET',\n headers: this.headers(),\n });\n\n if (!response.ok) return [];\n\n const data = (await response.json()) as {\n data?: {\n messages?: Array<{\n id: string;\n fromAgentId?: string;\n content?: string;\n conversationId?: string;\n createdAt?: string;\n }>;\n };\n };\n\n return (data.data?.messages ?? []).map((m) => ({\n id: m.id,\n from: m.fromAgentId ?? 'unknown',\n content: m.content ?? '',\n threadId: m.conversationId,\n timestamp: m.createdAt ?? new Date().toISOString(),\n }));\n }\n\n /**\n * Poll a group conversation for recent messages that @mention this agent.\n * This is the fix for the group @mention blind spot.\n */\n private async pollGroupConversation(conversationId: string): Promise<ConduitMessage[]> {\n const limit = this.config.groupPollLimit ?? DEFAULT_GROUP_POLL_LIMIT;\n const url = `${this.config.apiBaseUrl}/conversations/${conversationId}/messages?sort=desc&limit=${limit}`;\n\n const response = await fetch(url, {\n method: 'GET',\n headers: this.headers(),\n });\n\n if (!response.ok) return [];\n\n const data = (await response.json()) as {\n data?: {\n messages?: Array<{\n id: string;\n fromAgentId?: string;\n content?: string;\n conversationId?: string;\n createdAt?: string;\n }>;\n };\n };\n\n const mentionPattern = new RegExp(`@${this.config.agentId}\\\\b|@all\\\\b`, 'i');\n\n return (data.data?.messages ?? [])\n .filter((m) => {\n // Only deliver messages that mention us or @all\n const content = m.content ?? '';\n return mentionPattern.test(content) && m.fromAgentId !== this.config.agentId;\n })\n .map((m) => ({\n id: m.id,\n from: m.fromAgentId ?? 'unknown',\n content: m.content ?? '',\n threadId: m.conversationId ?? conversationId,\n timestamp: m.createdAt ?? new Date().toISOString(),\n }));\n }\n\n /** Build auth headers. */\n private headers(): Record<string, string> {\n return {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.config.apiKey}`,\n 'X-Agent-Id': this.config.agentId,\n };\n }\n}\n","/**\n * HeartbeatService — Periodic online status heartbeat.\n *\n * Sends a heartbeat to the SignalDock API at a configurable interval\n * to maintain the agent's online status. If the heartbeat fails,\n * it retries silently — the agent continues operating regardless.\n *\n * @task T218\n */\n\n/** Heartbeat service configuration. */\nexport interface HeartbeatConfig {\n /** Agent ID to send heartbeats for. */\n agentId: string;\n /** API key for authentication. */\n apiKey: string;\n /** API base URL. */\n apiBaseUrl: string;\n /** Heartbeat interval in milliseconds. Default: 30000 (30s). */\n intervalMs?: number;\n}\n\n/** Default heartbeat interval: 30 seconds. */\nconst DEFAULT_INTERVAL_MS = 30_000;\n\n/** HeartbeatService sends periodic online status to the cloud API. */\nexport class HeartbeatService {\n private config: HeartbeatConfig;\n private timer: ReturnType<typeof setInterval> | null = null;\n private running = false;\n private consecutiveFailures = 0;\n\n constructor(config: HeartbeatConfig) {\n this.config = config;\n }\n\n /** Start sending heartbeats at the configured interval. */\n start(): void {\n if (this.running) return;\n this.running = true;\n this.consecutiveFailures = 0;\n\n const intervalMs = this.config.intervalMs ?? DEFAULT_INTERVAL_MS;\n\n // Send initial heartbeat immediately\n void this.sendHeartbeat();\n\n this.timer = setInterval(() => {\n void this.sendHeartbeat();\n }, intervalMs);\n }\n\n /** Stop sending heartbeats. */\n stop(): void {\n this.running = false;\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n }\n\n /** Get heartbeat service status. */\n status(): { running: boolean; consecutiveFailures: number } {\n return {\n running: this.running,\n consecutiveFailures: this.consecutiveFailures,\n };\n }\n\n /** Send a single heartbeat to the cloud API. */\n private async sendHeartbeat(): Promise<void> {\n try {\n const response = await fetch(\n `${this.config.apiBaseUrl}/agents/${this.config.agentId}/heartbeat`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.config.apiKey}`,\n 'X-Agent-Id': this.config.agentId,\n },\n body: JSON.stringify({ status: 'online' }),\n signal: AbortSignal.timeout(10_000),\n },\n );\n\n if (response.ok) {\n this.consecutiveFailures = 0;\n } else {\n this.consecutiveFailures++;\n }\n } catch {\n this.consecutiveFailures++;\n }\n }\n}\n","/**\n * KeyRotationService — Automatic API key rotation based on credential age.\n *\n * Monitors the age of the agent's API key and triggers rotation when\n * the key exceeds the configured threshold. Uses the AgentRegistryAPI\n * to perform the actual rotation (which calls the cloud API and\n * re-encrypts the new key locally).\n *\n * @task T218\n */\n\nimport type { AgentRegistryAPI } from '@cleocode/contracts';\n\n/** Key rotation service configuration. */\nexport interface KeyRotationConfig {\n /** Agent ID to monitor. */\n agentId: string;\n /** AgentRegistryAPI instance for credential lookup and rotation. */\n registry: AgentRegistryAPI;\n /** Check interval in milliseconds. Default: 3600000 (1 hour). */\n checkIntervalMs?: number;\n /** Max key age in milliseconds before rotation. Default: 2592000000 (30 days). */\n maxKeyAgeMs?: number;\n}\n\n/** Default check interval: 1 hour. */\nconst DEFAULT_CHECK_INTERVAL_MS = 3_600_000;\n\n/** Default max key age: 30 days. */\nconst DEFAULT_MAX_KEY_AGE_MS = 30 * 24 * 60 * 60 * 1000;\n\n/** KeyRotationService monitors credential age and auto-rotates when threshold is exceeded. */\nexport class KeyRotationService {\n private config: KeyRotationConfig;\n private timer: ReturnType<typeof setInterval> | null = null;\n private running = false;\n private lastRotationAt: string | null = null;\n\n constructor(config: KeyRotationConfig) {\n this.config = config;\n }\n\n /** Start monitoring key age at the configured interval. */\n start(): void {\n if (this.running) return;\n this.running = true;\n\n const intervalMs = this.config.checkIntervalMs ?? DEFAULT_CHECK_INTERVAL_MS;\n\n // Initial check after a short delay (don't rotate immediately on startup)\n setTimeout(() => {\n void this.checkAndRotate();\n }, 5000);\n\n this.timer = setInterval(() => {\n void this.checkAndRotate();\n }, intervalMs);\n }\n\n /** Stop monitoring. */\n stop(): void {\n this.running = false;\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n }\n\n /** Get rotation service status. */\n status(): { running: boolean; lastRotationAt: string | null } {\n return {\n running: this.running,\n lastRotationAt: this.lastRotationAt,\n };\n }\n\n /** Check credential age and rotate if needed. */\n private async checkAndRotate(): Promise<void> {\n try {\n const credential = await this.config.registry.get(this.config.agentId);\n if (!credential) return;\n\n const maxAge = this.config.maxKeyAgeMs ?? DEFAULT_MAX_KEY_AGE_MS;\n const credentialAge = Date.now() - new Date(credential.updatedAt).getTime();\n\n if (credentialAge > maxAge) {\n await this.config.registry.rotateKey(this.config.agentId);\n this.lastRotationAt = new Date().toISOString();\n }\n } catch {\n // Rotation failure is non-fatal — will retry next interval\n }\n }\n}\n","/**\n * SseConnectionService — Persistent SSE connection manager.\n *\n * Maintains a persistent SSE connection to the SignalDock API for\n * real-time message delivery. Wraps SseTransport with lifecycle\n * management: start, stop, reconnect, and message forwarding.\n *\n * When SSE is available, messages arrive in real-time. When it falls\n * back to HTTP polling (managed by SseTransport internally), the\n * service continues operating transparently.\n *\n * @task T218\n */\n\nimport type { ConduitMessage, Transport } from '@cleocode/contracts';\n\n/** SSE connection service configuration. */\nexport interface SseConnectionConfig {\n /** Agent ID to connect as. */\n agentId: string;\n /** API key for authentication. */\n apiKey: string;\n /** API base URL. */\n apiBaseUrl: string;\n /** SSE endpoint URL. If omitted, uses apiBaseUrl + /sse. */\n sseEndpoint?: string;\n /** Transport instance to use. Injected by createRuntime. */\n transport: Transport;\n}\n\n/** Message handler callback. */\nexport type SseMessageHandler = (message: ConduitMessage) => void;\n\n/** SseConnectionService manages a persistent transport with subscribe() support. */\nexport class SseConnectionService {\n private config: SseConnectionConfig;\n private handler: SseMessageHandler | null = null;\n private unsubscribe: (() => void) | null = null;\n private running = false;\n\n constructor(config: SseConnectionConfig) {\n this.config = config;\n }\n\n /** Register a message handler for incoming messages. */\n onMessage(handler: SseMessageHandler): void {\n this.handler = handler;\n }\n\n /** Start the SSE connection. */\n async start(): Promise<void> {\n if (this.running) return;\n this.running = true;\n\n await this.config.transport.connect({\n agentId: this.config.agentId,\n apiKey: this.config.apiKey,\n apiBaseUrl: this.config.apiBaseUrl,\n sseEndpoint: this.config.sseEndpoint,\n });\n\n // Subscribe to incoming messages if transport supports it\n if (this.handler && this.config.transport.subscribe) {\n this.unsubscribe = this.config.transport.subscribe(this.handler);\n }\n }\n\n /** Stop the connection and clean up. */\n async stop(): Promise<void> {\n this.running = false;\n\n if (this.unsubscribe) {\n this.unsubscribe();\n this.unsubscribe = null;\n }\n\n await this.config.transport.disconnect();\n }\n\n /** Get connection service status. */\n status(): { running: boolean; transportName: string } {\n return {\n running: this.running,\n transportName: this.config.transport.name,\n };\n }\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@cleocode/runtime",
3
+ "version": "2026.4.0",
4
+ "description": "Long-running process layer for CLEO — agent polling, SSE connections, heartbeat, key rotation",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "dependencies": {
15
+ "@cleocode/contracts": "2026.4.0",
16
+ "@cleocode/core": "2026.4.0"
17
+ },
18
+ "devDependencies": {
19
+ "tsup": "^8.0.0",
20
+ "vitest": "^3.0.0"
21
+ },
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "scripts": {
29
+ "build": "tsup",
30
+ "test": "vitest run"
31
+ }
32
+ }