@cleocode/runtime 2026.4.6 → 2026.4.9
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/README.md +185 -0
- package/dist/index.js +25 -30
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# @cleocode/runtime
|
|
2
|
+
|
|
3
|
+
Long-running process layer for CLEO. Provides the daemon services that
|
|
4
|
+
let an agent stay resident on a host: message polling, SSE connection
|
|
5
|
+
management, heartbeat reporting, and credential key rotation.
|
|
6
|
+
|
|
7
|
+
This package powers `cleo agent start` — when an operator starts an
|
|
8
|
+
autonomous agent, this is the runtime that keeps it alive between
|
|
9
|
+
human interactions.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pnpm add @cleocode/runtime
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Public API
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import {
|
|
21
|
+
createRuntime,
|
|
22
|
+
type RuntimeConfig,
|
|
23
|
+
type RuntimeHandle,
|
|
24
|
+
AgentPoller,
|
|
25
|
+
HeartbeatService,
|
|
26
|
+
KeyRotationService,
|
|
27
|
+
SseConnectionService,
|
|
28
|
+
} from '@cleocode/runtime';
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### `createRuntime(registry, config)`
|
|
32
|
+
|
|
33
|
+
Top-level entry point. Resolves an agent credential from the registry,
|
|
34
|
+
configures the poller and ancillary services, opens the transport, and
|
|
35
|
+
starts polling. Returns a `RuntimeHandle` for registering message
|
|
36
|
+
handlers and stopping the runtime cleanly.
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { createRuntime } from '@cleocode/runtime';
|
|
40
|
+
import { AgentRegistryAPI } from '@cleocode/core/internal';
|
|
41
|
+
|
|
42
|
+
const registry = new AgentRegistryAPI(/* ... */);
|
|
43
|
+
const handle = await createRuntime(registry, {
|
|
44
|
+
agentId: 'cleo-prime',
|
|
45
|
+
pollIntervalMs: 5000,
|
|
46
|
+
heartbeatIntervalMs: 30000,
|
|
47
|
+
groupConversationIds: ['general', 'announcements'],
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
handle.poller.onMessage(async (msg) => {
|
|
51
|
+
// handle inbound messages
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// later
|
|
55
|
+
handle.stop();
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### `RuntimeConfig`
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
interface RuntimeConfig {
|
|
62
|
+
agentId?: string; // defaults to most recently active agent
|
|
63
|
+
pollIntervalMs?: number; // default: 5000
|
|
64
|
+
groupConversationIds?: string[];
|
|
65
|
+
groupPollLimit?: number; // default: 15
|
|
66
|
+
heartbeatIntervalMs?: number; // default: 30000, 0 to disable
|
|
67
|
+
maxKeyAgeMs?: number; // default: 30 days, 0 to disable
|
|
68
|
+
sseEndpoint?: string;
|
|
69
|
+
createSseTransport?: () => Transport;
|
|
70
|
+
transport?: Transport; // pre-created, bypasses auto-resolution
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### `RuntimeHandle`
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
interface RuntimeHandle {
|
|
78
|
+
poller: AgentPoller;
|
|
79
|
+
heartbeat: HeartbeatService | null;
|
|
80
|
+
keyRotation: KeyRotationService | null;
|
|
81
|
+
sseConnection: SseConnectionService | null;
|
|
82
|
+
transport: Transport;
|
|
83
|
+
agentId: string;
|
|
84
|
+
stop: () => void;
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`stop()` cleanly shuts down all services in order: SSE connection,
|
|
89
|
+
heartbeat, key rotation, poller, transport.
|
|
90
|
+
|
|
91
|
+
## Services
|
|
92
|
+
|
|
93
|
+
### `AgentPoller`
|
|
94
|
+
|
|
95
|
+
The core polling loop. Fetches direct messages and group-mention
|
|
96
|
+
messages on a configurable interval, deduplicates against a high-water
|
|
97
|
+
mark, and dispatches each new message to registered handlers.
|
|
98
|
+
|
|
99
|
+
### `HeartbeatService`
|
|
100
|
+
|
|
101
|
+
Periodic agent presence reporting. Tells the SignalDock backend that
|
|
102
|
+
the agent is alive so other agents can route messages to it without
|
|
103
|
+
hitting offline timeouts. Disabled when `heartbeatIntervalMs === 0`.
|
|
104
|
+
|
|
105
|
+
### `KeyRotationService`
|
|
106
|
+
|
|
107
|
+
Watches the agent's transport credential age and rotates the API key
|
|
108
|
+
before it expires. Maintains zero-downtime rotation by overlapping the
|
|
109
|
+
old and new keys for one poll cycle. Disabled when `maxKeyAgeMs === 0`.
|
|
110
|
+
|
|
111
|
+
### `SseConnectionService`
|
|
112
|
+
|
|
113
|
+
Optional persistent Server-Sent Events connection for sub-second
|
|
114
|
+
message delivery. When configured, the poller throttles down to a
|
|
115
|
+
slower interval and SSE handles the realtime path. Falls back
|
|
116
|
+
automatically to pure polling on connection loss.
|
|
117
|
+
|
|
118
|
+
## Architecture
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
┌──────────────────────────────────────────┐
|
|
122
|
+
│ cleo agent start (CLI entry) │
|
|
123
|
+
└────────────────┬─────────────────────────┘
|
|
124
|
+
│
|
|
125
|
+
▼
|
|
126
|
+
┌──────────────────────────────────────────┐
|
|
127
|
+
│ @cleocode/runtime createRuntime() │
|
|
128
|
+
│ ┌────────────────────────────────┐ │
|
|
129
|
+
│ │ AgentPoller (poll loop) │ │
|
|
130
|
+
│ │ HeartbeatService (presence) │ │
|
|
131
|
+
│ │ KeyRotationService (creds) │ │
|
|
132
|
+
│ │ SseConnectionService (realtime)│ │
|
|
133
|
+
│ └────────────────────────────────┘ │
|
|
134
|
+
└────────────────┬─────────────────────────┘
|
|
135
|
+
│
|
|
136
|
+
▼
|
|
137
|
+
┌──────────────────────────────────────────┐
|
|
138
|
+
│ @cleocode/contracts Transport interface │
|
|
139
|
+
│ resolves to Local | SSE | HTTP │
|
|
140
|
+
└────────────────┬─────────────────────────┘
|
|
141
|
+
│
|
|
142
|
+
▼
|
|
143
|
+
┌──────────────────────────────────────────┐
|
|
144
|
+
│ SignalDock backend (local or cloud) │
|
|
145
|
+
└──────────────────────────────────────────┘
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The runtime is **transport-agnostic**: it works with the local
|
|
149
|
+
napi-rs SignalDock binding (in-process), SSE realtime, or HTTP polling
|
|
150
|
+
against `api.signaldock.io`. The transport layer is selected by
|
|
151
|
+
`createRuntime()` based on whether `sseEndpoint` and `transport` are
|
|
152
|
+
present in the config.
|
|
153
|
+
|
|
154
|
+
## CANT profile execution
|
|
155
|
+
|
|
156
|
+
`createRuntime()` does **not** execute `.cant` workflow profiles
|
|
157
|
+
directly. Profile-driven workflow execution lives in the
|
|
158
|
+
[`cant-bridge.ts`](../cleo/templates/cleoos-hub/pi-extensions/cant-bridge.ts)
|
|
159
|
+
Pi extension and runs inside a Pi session, not the daemon. Operators
|
|
160
|
+
who want profile-driven behaviour should start a Pi session and use
|
|
161
|
+
`/cant:load <file>` followed by `/cant:run <file> <workflow>`.
|
|
162
|
+
|
|
163
|
+
This boundary was set in [ADR-035 §D5](../../.cleo/adrs/ADR-035-pi-v2-v3-harness.md)
|
|
164
|
+
"single engine, cant-bridge.ts as canonical" — runtime stays simple
|
|
165
|
+
and profile semantics live in one place.
|
|
166
|
+
|
|
167
|
+
## Testing
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
pnpm --filter @cleocode/runtime test
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Tests use mocked transports from `@cleocode/contracts` to exercise the
|
|
174
|
+
poller and service lifecycle without hitting a real backend.
|
|
175
|
+
|
|
176
|
+
## Related
|
|
177
|
+
|
|
178
|
+
- [`@cleocode/contracts`](../contracts) — Transport interface and message types
|
|
179
|
+
- [`@cleocode/core`](../core) — agent registry, credential storage, message persistence
|
|
180
|
+
- [`packages/cleo/src/cli/commands/agent.ts`](../cleo/src/cli/commands/agent.ts) — `cleo agent start` CLI entry that calls `createRuntime`
|
|
181
|
+
- [`.cleo/adrs/ADR-035-pi-v2-v3-harness.md`](../../.cleo/adrs/ADR-035-pi-v2-v3-harness.md) — runtime/Pi boundary decisions
|
|
182
|
+
|
|
183
|
+
## License
|
|
184
|
+
|
|
185
|
+
MIT
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { conduit } from "@cleocode/core";
|
|
3
3
|
|
|
4
|
-
// src/services/agent-poller.
|
|
4
|
+
// src/services/agent-poller.ts
|
|
5
5
|
var DEFAULT_POLL_INTERVAL = 5e3;
|
|
6
6
|
var DEFAULT_GROUP_POLL_LIMIT = 15;
|
|
7
7
|
var AgentPoller = class {
|
|
@@ -19,8 +19,7 @@ var AgentPoller = class {
|
|
|
19
19
|
}
|
|
20
20
|
/** Start the polling loop. */
|
|
21
21
|
start() {
|
|
22
|
-
if (this.running)
|
|
23
|
-
return;
|
|
22
|
+
if (this.running) return;
|
|
24
23
|
this.running = true;
|
|
25
24
|
const intervalMs = this.config.pollIntervalMs ?? DEFAULT_POLL_INTERVAL;
|
|
26
25
|
void this.pollCycle();
|
|
@@ -45,8 +44,7 @@ var AgentPoller = class {
|
|
|
45
44
|
}
|
|
46
45
|
/** Single poll cycle — peek + group conversations. */
|
|
47
46
|
async pollCycle() {
|
|
48
|
-
if (!this.handler)
|
|
49
|
-
return;
|
|
47
|
+
if (!this.handler) return;
|
|
50
48
|
const newMessages = [];
|
|
51
49
|
try {
|
|
52
50
|
const peekMessages = await this.peekMessages();
|
|
@@ -92,8 +90,7 @@ var AgentPoller = class {
|
|
|
92
90
|
method: "GET",
|
|
93
91
|
headers: this.headers()
|
|
94
92
|
});
|
|
95
|
-
if (!response.ok)
|
|
96
|
-
return [];
|
|
93
|
+
if (!response.ok) return [];
|
|
97
94
|
const data = await response.json();
|
|
98
95
|
return (data.data?.messages ?? []).map((m) => ({
|
|
99
96
|
id: m.id,
|
|
@@ -114,8 +111,7 @@ var AgentPoller = class {
|
|
|
114
111
|
method: "GET",
|
|
115
112
|
headers: this.headers()
|
|
116
113
|
});
|
|
117
|
-
if (!response.ok)
|
|
118
|
-
return [];
|
|
114
|
+
if (!response.ok) return [];
|
|
119
115
|
const data = await response.json();
|
|
120
116
|
const mentionPattern = new RegExp(`@${this.config.agentId}\\b|@all\\b`, "i");
|
|
121
117
|
return (data.data?.messages ?? []).filter((m) => {
|
|
@@ -139,7 +135,7 @@ var AgentPoller = class {
|
|
|
139
135
|
}
|
|
140
136
|
};
|
|
141
137
|
|
|
142
|
-
// src/services/heartbeat.
|
|
138
|
+
// src/services/heartbeat.ts
|
|
143
139
|
var DEFAULT_INTERVAL_MS = 3e4;
|
|
144
140
|
var HeartbeatService = class {
|
|
145
141
|
config;
|
|
@@ -151,8 +147,7 @@ var HeartbeatService = class {
|
|
|
151
147
|
}
|
|
152
148
|
/** Start sending heartbeats at the configured interval. */
|
|
153
149
|
start() {
|
|
154
|
-
if (this.running)
|
|
155
|
-
return;
|
|
150
|
+
if (this.running) return;
|
|
156
151
|
this.running = true;
|
|
157
152
|
this.consecutiveFailures = 0;
|
|
158
153
|
const intervalMs = this.config.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
@@ -179,16 +174,19 @@ var HeartbeatService = class {
|
|
|
179
174
|
/** Send a single heartbeat to the cloud API. */
|
|
180
175
|
async sendHeartbeat() {
|
|
181
176
|
try {
|
|
182
|
-
const response = await fetch(
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
+
);
|
|
192
190
|
if (response.ok) {
|
|
193
191
|
this.consecutiveFailures = 0;
|
|
194
192
|
} else {
|
|
@@ -200,7 +198,7 @@ var HeartbeatService = class {
|
|
|
200
198
|
}
|
|
201
199
|
};
|
|
202
200
|
|
|
203
|
-
// src/services/key-rotation.
|
|
201
|
+
// src/services/key-rotation.ts
|
|
204
202
|
var DEFAULT_CHECK_INTERVAL_MS = 36e5;
|
|
205
203
|
var DEFAULT_MAX_KEY_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
206
204
|
var KeyRotationService = class {
|
|
@@ -213,8 +211,7 @@ var KeyRotationService = class {
|
|
|
213
211
|
}
|
|
214
212
|
/** Start monitoring key age at the configured interval. */
|
|
215
213
|
start() {
|
|
216
|
-
if (this.running)
|
|
217
|
-
return;
|
|
214
|
+
if (this.running) return;
|
|
218
215
|
this.running = true;
|
|
219
216
|
const intervalMs = this.config.checkIntervalMs ?? DEFAULT_CHECK_INTERVAL_MS;
|
|
220
217
|
setTimeout(() => {
|
|
@@ -243,8 +240,7 @@ var KeyRotationService = class {
|
|
|
243
240
|
async checkAndRotate() {
|
|
244
241
|
try {
|
|
245
242
|
const credential = await this.config.registry.get(this.config.agentId);
|
|
246
|
-
if (!credential)
|
|
247
|
-
return;
|
|
243
|
+
if (!credential) return;
|
|
248
244
|
const maxAge = this.config.maxKeyAgeMs ?? DEFAULT_MAX_KEY_AGE_MS;
|
|
249
245
|
const credentialAge = Date.now() - new Date(credential.updatedAt).getTime();
|
|
250
246
|
if (credentialAge > maxAge) {
|
|
@@ -256,7 +252,7 @@ var KeyRotationService = class {
|
|
|
256
252
|
}
|
|
257
253
|
};
|
|
258
254
|
|
|
259
|
-
// src/services/sse-connection.
|
|
255
|
+
// src/services/sse-connection.ts
|
|
260
256
|
var SseConnectionService = class {
|
|
261
257
|
config;
|
|
262
258
|
handler = null;
|
|
@@ -271,8 +267,7 @@ var SseConnectionService = class {
|
|
|
271
267
|
}
|
|
272
268
|
/** Start the SSE connection. */
|
|
273
269
|
async start() {
|
|
274
|
-
if (this.running)
|
|
275
|
-
return;
|
|
270
|
+
if (this.running) return;
|
|
276
271
|
this.running = true;
|
|
277
272
|
await this.config.transport.connect({
|
|
278
273
|
agentId: this.config.agentId,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +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;\n\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":[]}
|
|
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;\n\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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cleocode/runtime",
|
|
3
|
-
"version": "2026.4.
|
|
3
|
+
"version": "2026.4.9",
|
|
4
4
|
"description": "Long-running process layer for CLEO — agent polling, SSE connections, heartbeat, key rotation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@cleocode/contracts": "2026.4.
|
|
16
|
-
"@cleocode/core": "2026.4.
|
|
15
|
+
"@cleocode/contracts": "2026.4.9",
|
|
16
|
+
"@cleocode/core": "2026.4.9"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"tsup": "^8.0.0",
|