@cleocode/runtime 2026.4.7 → 2026.4.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +185 -0
  2. 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cleocode/runtime",
3
- "version": "2026.4.7",
3
+ "version": "2026.4.10",
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.7",
16
- "@cleocode/core": "2026.4.7"
15
+ "@cleocode/contracts": "2026.4.10",
16
+ "@cleocode/core": "2026.4.10"
17
17
  },
18
18
  "devDependencies": {
19
19
  "tsup": "^8.0.0",