@cleocode/runtime 2026.10.4 → 2026.10.5-rc.ci.672

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 (36) hide show
  1. package/dist/{chunk-L5BLVQDA.js → chunk-4PUSKGDO.js} +2 -2
  2. package/dist/chunk-4PUSKGDO.js.map +1 -0
  3. package/dist/{chunk-KQBVXGOL.js → chunk-GULKX44R.js} +5 -3
  4. package/dist/chunk-GULKX44R.js.map +1 -0
  5. package/dist/{chunk-7Q7BEWM7.js → chunk-HXTRXEFI.js} +7 -9
  6. package/dist/chunk-HXTRXEFI.js.map +1 -0
  7. package/dist/{chunk-U2ESNIJ5.js → chunk-NOPLC5WS.js} +4 -2
  8. package/dist/chunk-NOPLC5WS.js.map +1 -0
  9. package/dist/chunk-PDZ5T4LO.js +597 -0
  10. package/dist/chunk-PDZ5T4LO.js.map +1 -0
  11. package/dist/{chunk-XJOMQIXL.js → chunk-WO6LJMDC.js} +4 -2
  12. package/dist/chunk-WO6LJMDC.js.map +1 -0
  13. package/dist/daemon/index.js +1 -1
  14. package/dist/dispatch-BWjVeeMg.d.ts +290 -0
  15. package/dist/gateway/dispatch.d.ts +5 -0
  16. package/dist/gateway/dispatch.js +53 -0
  17. package/dist/gateway/dispatch.js.map +1 -0
  18. package/dist/gateway/http/index.d.ts +897 -11
  19. package/dist/gateway/http/index.js +2 -2
  20. package/dist/gateway/index.d.ts +549 -4
  21. package/dist/gateway/index.js +26 -581
  22. package/dist/gateway/index.js.map +1 -1
  23. package/dist/gateway/mcp/index.d.ts +232 -10
  24. package/dist/gateway/mcp/index.js +2 -2
  25. package/dist/gateway/rpc/index.d.ts +5 -2036
  26. package/dist/gateway/rpc/index.js +1 -1
  27. package/dist/gateway-handler-DCU6E0a2.d.ts +80 -0
  28. package/dist/index.d.ts +4 -3
  29. package/dist/index.js +9 -6
  30. package/dist/index.js.map +1 -1
  31. package/package.json +7 -3
  32. package/dist/chunk-7Q7BEWM7.js.map +0 -1
  33. package/dist/chunk-KQBVXGOL.js.map +0 -1
  34. package/dist/chunk-L5BLVQDA.js.map +0 -1
  35. package/dist/chunk-U2ESNIJ5.js.map +0 -1
  36. package/dist/chunk-XJOMQIXL.js.map +0 -1
@@ -5,7 +5,7 @@ import {
5
5
  encodeFrame,
6
6
  routeFrame,
7
7
  startRpcServer
8
- } from "../../chunk-KQBVXGOL.js";
8
+ } from "../../chunk-GULKX44R.js";
9
9
  export {
10
10
  LineBuffer,
11
11
  buildErrorFrame,
@@ -0,0 +1,80 @@
1
+ import { DomainHandler, Middleware, DispatchRequest, DispatchResponse } from '@cleocode/contracts/gateway';
2
+
3
+ /**
4
+ * Central Dispatcher -- Routes requests through middleware to domain handlers.
5
+ *
6
+ * The dispatcher is the single entry point for the CLI adapter.
7
+ * It resolves operations, validates parameters, runs the middleware pipeline,
8
+ * and delegates to the appropriate domain handler.
9
+ *
10
+ * Flow: DispatchRequest → resolve → param validation → middleware → handler
11
+ *
12
+ * ## T1088 — Dialectic Evaluator Hook (PSYCHE Wave 3)
13
+ *
14
+ * After each successful mutate operation, the dispatcher fires a background
15
+ * `evaluateDialectic` call via `setImmediate` if all of the following apply:
16
+ *
17
+ * 1. The response indicates `success: true`.
18
+ * 2. The request gateway is `"mutate"` (state-modifying user-facing commands).
19
+ * 3. A `sessionId` is present on the request (identifies conversation context).
20
+ * 4. The in-memory rate limiter allows the evaluation (1 per 10s per session).
21
+ *
22
+ * Errors from the background evaluation are caught and logged at `warn` level
23
+ * so they never affect the caller.
24
+ *
25
+ * ## T12024 — Bounded deadline for background LLM calls
26
+ *
27
+ * The dialectic evaluation makes an HTTP call to an LLM backend (Ollama,
28
+ * Anthropic, etc.) which keeps the Node.js event loop alive. After the CLI
29
+ * success-path `finally` tears down workers and databases via
30
+ * `shutdownCliRuntime`, the pending HTTP connection is the sole remaining
31
+ * reference — preventing the process from draining the loop and exiting rc:0.
32
+ *
33
+ * Each evaluation now runs against an unrefed deadline so the deadline
34
+ * timer itself never holds the loop open, while the abort signal cancels the
35
+ * underlying HTTP request when the deadline fires, releasing the connection
36
+ * and allowing natural exit. The timeout is generous enough for normal
37
+ * responses but prevents hung connections from blocking shutdown.
38
+ *
39
+ * @epic T4820
40
+ * @task T1088
41
+ * @task T12024
42
+ */
43
+
44
+ interface DispatcherConfig {
45
+ handlers: Map<string, DomainHandler>;
46
+ middlewares?: Middleware[];
47
+ }
48
+ declare class Dispatcher {
49
+ private handlers;
50
+ private pipeline;
51
+ constructor(config: DispatcherConfig);
52
+ dispatch(request: DispatchRequest): Promise<DispatchResponse>;
53
+ }
54
+
55
+ /**
56
+ * The transport-neutral gateway handler (relocated from `gateway/index.ts`,
57
+ * T13126) so the light `@cleocode/runtime/gateway/dispatch` entry can export
58
+ * it without loading the HTTP, MCP and RPC transports.
59
+ *
60
+ * @task T13126
61
+ */
62
+
63
+ /**
64
+ * Transport-agnostic gateway entrypoint. Wraps a configured {@link Dispatcher}
65
+ * (`compose(middlewares) → DomainHandler`) behind a single `handle()` call that
66
+ * every transport adapter (CLI/MCP/RPC/HTTP) invokes uniformly. The adapter
67
+ * owns wire concerns (error-render, `process.exit`, serialization); the handler
68
+ * only resolves, validates, runs middleware, and returns a {@link DispatchResponse}.
69
+ */
70
+ interface GatewayHandler {
71
+ /** Route one gateway request through the dispatch pipeline. */
72
+ handle(req: DispatchRequest): Promise<DispatchResponse>;
73
+ }
74
+ /**
75
+ * Build a {@link GatewayHandler} from a {@link DispatcherConfig} (injected
76
+ * domain handlers + middleware). The returned handler is transport-neutral.
77
+ */
78
+ declare function createGatewayHandler(config: DispatcherConfig): GatewayHandler;
79
+
80
+ export { Dispatcher as D, type GatewayHandler as G, type DispatcherConfig as a, createGatewayHandler as c };
package/dist/index.d.ts CHANGED
@@ -74,9 +74,10 @@ declare class AgentPoller {
74
74
  /**
75
75
  * HeartbeatService — Periodic online status heartbeat.
76
76
  *
77
- * Sends a heartbeat to the SignalDock API at a configurable interval
77
+ * Sends a heartbeat to the agent's cloud API at a configurable interval
78
78
  * to maintain the agent's online status. If the heartbeat fails,
79
79
  * it retries silently — the agent continues operating regardless.
80
+ * A retired SignalDock host is refused before any network I/O (T13169).
80
81
  *
81
82
  * @task T218
82
83
  */
@@ -304,9 +305,9 @@ interface RuntimeConfig {
304
305
  interface RuntimeHandle {
305
306
  /** The AgentPoller instance. */
306
307
  poller: AgentPoller;
307
- /** The HeartbeatService instance (null if disabled). */
308
+ /** The HeartbeatService instance (null if disabled or the base URL is a retired SignalDock host). */
308
309
  heartbeat: HeartbeatService | null;
309
- /** The KeyRotationService instance (null if disabled). */
310
+ /** The KeyRotationService instance (null if disabled or the base URL is a retired SignalDock host). */
310
311
  keyRotation: KeyRotationService | null;
311
312
  /** The SseConnectionService instance (null if no SSE endpoint). */
312
313
  sseConnection: SseConnectionService | null;
package/dist/index.js CHANGED
@@ -2,12 +2,13 @@ import {
2
2
  SubsystemRegistry,
3
3
  createSubsystemLogger,
4
4
  defineSubsystem
5
- } from "./chunk-7Q7BEWM7.js";
5
+ } from "./chunk-HXTRXEFI.js";
6
6
 
7
7
  // src/index.ts
8
8
  import { conduit } from "@cleocode/core";
9
9
 
10
10
  // src/services/agent-poller.ts
11
+ import { conduitFetch } from "@cleocode/core/conduit/cloud-endpoint.js";
11
12
  var DEFAULT_POLL_INTERVAL = 5e3;
12
13
  var DEFAULT_GROUP_POLL_LIMIT = 15;
13
14
  var AgentPoller = class {
@@ -92,7 +93,7 @@ var AgentPoller = class {
92
93
  params.set("mentioned", this.config.agentId);
93
94
  params.set("limit", "50");
94
95
  const url = `${this.config.apiBaseUrl}/messages/peek?${params}`;
95
- const response = await fetch(url, {
96
+ const response = await conduitFetch(url, {
96
97
  method: "GET",
97
98
  headers: this.headers()
98
99
  });
@@ -113,7 +114,7 @@ var AgentPoller = class {
113
114
  async pollGroupConversation(conversationId) {
114
115
  const limit = this.config.groupPollLimit ?? DEFAULT_GROUP_POLL_LIMIT;
115
116
  const url = `${this.config.apiBaseUrl}/conversations/${conversationId}/messages?sort=desc&limit=${limit}`;
116
- const response = await fetch(url, {
117
+ const response = await conduitFetch(url, {
117
118
  method: "GET",
118
119
  headers: this.headers()
119
120
  });
@@ -142,6 +143,7 @@ var AgentPoller = class {
142
143
  };
143
144
 
144
145
  // src/services/heartbeat.ts
146
+ import { conduitFetch as conduitFetch2 } from "@cleocode/core/conduit/cloud-endpoint.js";
145
147
  var DEFAULT_INTERVAL_MS = 3e4;
146
148
  var HeartbeatService = class {
147
149
  config;
@@ -180,7 +182,7 @@ var HeartbeatService = class {
180
182
  /** Send a single heartbeat to the cloud API. */
181
183
  async sendHeartbeat() {
182
184
  try {
183
- const response = await fetch(
185
+ const response = await conduitFetch2(
184
186
  `${this.config.apiBaseUrl}/agents/${this.config.agentId}/heartbeat`,
185
187
  {
186
188
  method: "POST",
@@ -580,8 +582,9 @@ async function createRuntime(registry, config) {
580
582
  transport
581
583
  };
582
584
  const poller = new AgentPoller(pollerConfig);
585
+ const cloudRetired = conduit.isRetiredCloudUrl(credential.apiBaseUrl);
583
586
  let heartbeat = null;
584
- if (config?.heartbeatIntervalMs !== 0) {
587
+ if (config?.heartbeatIntervalMs !== 0 && !cloudRetired) {
585
588
  heartbeat = new HeartbeatService({
586
589
  agentId: credential.agentId,
587
590
  apiKey: credential.apiKey,
@@ -591,7 +594,7 @@ async function createRuntime(registry, config) {
591
594
  heartbeat.start();
592
595
  }
593
596
  let keyRotation = null;
594
- if (config?.maxKeyAgeMs !== 0) {
597
+ if (config?.maxKeyAgeMs !== 0 && !cloudRetired) {
595
598
  keyRotation = new KeyRotationService({
596
599
  agentId: credential.agentId,
597
600
  registry,
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","../src/sentient-subsystem.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\n// R4 (T11255) — sentient daemon subsystem adapter\nexport {\n bootstrapSentientRegistry,\n createSentientSubsystem,\n} from './sentient-subsystem.js';\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","/**\n * Sentient daemon subsystem adapter — R4 (T11255).\n *\n * Expresses the full sentient daemon lifecycle (Studio supervision, Tier-1\n * tick cron, Tier-2 proposal cron, skill curator cron, nightly hygiene loop)\n * as a single {@link Subsystem} via `defineSubsystem` from\n * `@cleocode/runtime/daemon`.\n *\n * ### Package placement\n * This file lives in `@cleocode/runtime` (NOT `@cleocode/core`) to respect the\n * dependency direction: `@cleocode/runtime` → `@cleocode/core` (runtime\n * consumes core; core must not import runtime to avoid a circular dep).\n *\n * ### Standalone-daemon path (daemon-entry.ts)\n * The legacy `bootstrapDaemon` in `@cleocode/core/sentient` continues to\n * function as the entry point for the standalone `cleo daemon start --foreground`\n * path. `createSentientSubsystem` is the adapter for hosts that want to embed\n * the sentient daemon inside a `SubsystemRegistry`-driven process.\n *\n * @packageDocumentation\n * @module @cleocode/runtime\n *\n * @epic T11255 R4 — migrate sentient/daemon.ts → daemon subsystem\n * @saga T11243 SG-RUNTIME-UNIFICATION\n */\n\nimport type { FSWatcher } from 'node:fs';\nimport { watch } from 'node:fs';\nimport { join } from 'node:path';\nimport type { Subsystem, SubsystemHealth } from '@cleocode/contracts';\nimport {\n acquireLock,\n type BootstrapDaemonOptions,\n curatorCronExpression,\n patchSentientState,\n readCuratorConfig,\n readSentientState,\n readSuperviseStudioConfig,\n releaseLock,\n reVerifyWorkerReport,\n SENTIENT_CRON_EXPR,\n SENTIENT_HYGIENE_CRON_EXPR,\n SENTIENT_LOCK_FILE,\n SENTIENT_PROPOSE_CRON_EXPR,\n SENTIENT_STATE_FILE,\n StudioSupervisor,\n safeRunCrossProjectHygiene,\n safeRunProposeTick,\n safeRunTick,\n warmupWorktreeBackend,\n} from '@cleocode/core/sentient';\nimport cron from 'node-cron';\nimport { createSubsystemLogger, defineSubsystem, SubsystemRegistry } from './daemon/index.js';\n\n// Internal logger for the subsystem.\nconst log = createSubsystemLogger('sentient');\n\n/**\n * Runtime context threaded from `start()` into `shutdown()`.\n *\n * Holds every handle the shutdown path needs to tear down cleanly.\n * Kept internal to this module — callers interact via `SubsystemRegistry`.\n */\ninterface SentientContext {\n readonly lockPath: string;\n readonly statePath: string;\n readonly studioSupervisor: StudioSupervisor | null;\n readonly watcher: FSWatcher | null;\n readonly lock: Awaited<ReturnType<typeof acquireLock>>;\n}\n\n/**\n * Declare the sentient daemon as a supervised daemon {@link Subsystem}.\n *\n * The returned subsystem (frozen by `defineSubsystem`) is registered with a\n * `SubsystemRegistry` in a host process. It carries the full lifecycle\n * that the legacy `bootstrapDaemon` ran inline:\n *\n * 1. `start()` — acquires the advisory lock, persists pid/startedAt,\n * optionally starts Studio supervision, watches the state file, and\n * schedules all four cron jobs (Tier-1, Tier-2, curator, hygiene). Runs\n * one boot tick before returning.\n * 2. `healthProbe()` — returns a `SubsystemHealth` row based on the pid\n * recorded in state.json.\n * 3. `shutdown()` — cascades Studio SIGTERM → watcher.close() → state patch\n * → lock release.\n *\n * @param projectRoot - Absolute path to the project (contains `.cleo/`).\n * @param opts - Optional overrides for Studio supervision and config path.\n * @returns A frozen {@link Subsystem} for `SubsystemRegistry.register()`.\n */\nexport function createSentientSubsystem(\n projectRoot: string,\n opts: BootstrapDaemonOptions = {},\n): Subsystem<SentientContext> {\n const statePath = join(projectRoot, SENTIENT_STATE_FILE);\n const lockPath = join(projectRoot, SENTIENT_LOCK_FILE);\n\n return defineSubsystem<SentientContext>({\n name: 'sentient',\n\n async start(): Promise<SentientContext> {\n // 1. Advisory lock.\n const lock = await acquireLock(lockPath);\n if (!lock) {\n log.error({}, 'lock acquisition failed — another daemon is running');\n process.exit(2);\n }\n\n // 2. Persist pid + startedAt.\n await patchSentientState(statePath, {\n pid: process.pid,\n startedAt: new Date().toISOString(),\n });\n\n // 3. Studio supervision.\n let studioSupervisor: StudioSupervisor | null = null;\n let shouldSuperviseStudio: boolean;\n if (typeof opts.superviseStudio === 'boolean') {\n shouldSuperviseStudio = opts.superviseStudio;\n } else {\n const { getCleoHome } = await import('@cleocode/core');\n const defaultConfigPath = join(getCleoHome(), 'config.json');\n const configPath = opts.globalConfigPath ?? defaultConfigPath;\n shouldSuperviseStudio = await readSuperviseStudioConfig(configPath);\n }\n\n if (shouldSuperviseStudio) {\n studioSupervisor = new StudioSupervisor(opts.studioOptions ?? {});\n try {\n studioSupervisor.start();\n log.info({}, 'Studio supervision enabled');\n } catch (err) {\n log.error({ err }, 'Studio supervision start failed (non-fatal; ticks continue)');\n studioSupervisor = null;\n }\n } else {\n log.info({}, 'Studio supervision disabled (daemon.superviseStudio=false)');\n }\n\n // 4. State file watcher.\n let watcher: FSWatcher | null = null;\n try {\n watcher = watch(statePath, { persistent: false }, () => {\n // Next tick will re-read state and honour kill-switch.\n });\n } catch {\n watcher = null;\n }\n\n // 5. Warm up worktree backend.\n await warmupWorktreeBackend();\n\n // 6. Boot tick (Tier-1 immediate run).\n // Wire reVerify (AC2/T11497) so the T1589 re-verify gate re-runs gates\n // on worker exit=0 instead of trusting the self-report.\n // Wire scope filter (AC1/T11497) for headless / walk-away autopilot.\n const tickOptions = {\n projectRoot,\n statePath,\n reVerify: reVerifyWorkerReport,\n scopeSagaId: opts.scopeSagaId,\n scopeEpicId: opts.scopeEpicId,\n };\n await patchSentientState(statePath, { lastCronFiredAt: new Date().toISOString() });\n const outcome = await safeRunTick(tickOptions);\n log.info(\n { kind: outcome.kind, taskId: outcome.taskId ?? null },\n `boot tick: ${outcome.kind} (task=${outcome.taskId ?? 'n/a'}) ${outcome.detail}`,\n );\n\n // 7. Schedule cron jobs.\n\n // Tier-1: every 5 minutes.\n cron.schedule(\n SENTIENT_CRON_EXPR,\n async () => {\n try {\n await patchSentientState(statePath, { lastCronFiredAt: new Date().toISOString() });\n } catch (err) {\n log.warn({ err }, 'heartbeat write failed');\n }\n try {\n const result = await safeRunTick(tickOptions);\n log.info(\n { kind: result.kind, taskId: result.taskId ?? null },\n `tick: ${result.kind} (task=${result.taskId ?? 'n/a'}) ${result.detail}`,\n );\n } catch (err) {\n log.error({ err }, 'tick error (caught at cron boundary)');\n }\n },\n { timezone: 'UTC', noOverlap: true, name: 'cleo-sentient' },\n );\n\n // Tier-2: every 2 hours.\n const proposeOptions = { projectRoot, statePath };\n cron.schedule(\n SENTIENT_PROPOSE_CRON_EXPR,\n async () => {\n try {\n const state = await readSentientState(statePath);\n if (!state.tier2Enabled) return;\n const result = await safeRunProposeTick(proposeOptions);\n log.info(\n { written: result.written, count: result.count },\n `propose: ${result.kind} ${result.detail}`,\n );\n } catch (err) {\n log.error({ err }, 'propose error (caught at cron boundary)');\n }\n },\n { timezone: 'UTC', noOverlap: true, name: 'cleo-sentient-propose' },\n );\n\n // Skill curator: opt-in, default off.\n {\n const { getCleoHome } = await import('@cleocode/core');\n const curatorConfigPath = opts.globalConfigPath ?? join(getCleoHome(), 'config.json');\n const curatorCfg = await readCuratorConfig(curatorConfigPath);\n if (curatorCfg.enabled) {\n const expr = curatorCronExpression(curatorCfg.runEveryHours);\n log.info({ interval: curatorCfg.runEveryHours, expr }, `curator enabled`);\n cron.schedule(\n expr,\n async () => {\n try {\n const curatorState = await readSentientState(statePath);\n if (curatorState.killSwitch) return;\n const { runCuratorTick } = await import('@cleocode/core/sentient');\n const result = await runCuratorTick({\n staleAfterDays: curatorCfg.staleAfterDays,\n archiveAfterDays: curatorCfg.archiveAfterDays,\n });\n log.info(\n { checked: result.summary.checked, stale: result.summary.markedStale },\n 'curator tick complete',\n );\n } catch (err) {\n log.error({ err }, 'curator error (caught at cron boundary)');\n }\n },\n { timezone: 'UTC', noOverlap: true, name: 'cleo-sentient-curator' },\n );\n } else {\n log.info({}, 'curator disabled (daemon.curator.enabled=false)');\n }\n }\n\n // Nightly hygiene: 02:00 UTC (or CLEO_HYGIENE_CRON override).\n cron.schedule(\n SENTIENT_HYGIENE_CRON_EXPR,\n async () => {\n try {\n const hygieneState = await readSentientState(statePath);\n if (hygieneState.killSwitch) return;\n log.info({}, 'nightly cross-project hygiene loop starting');\n const digest = await safeRunCrossProjectHygiene();\n await patchSentientState(statePath, {\n hygieneLastRunAt: digest.completedAt,\n hygieneSummary: digest.summary,\n hygieneStats: {\n projectsChecked: digest.nexusIntegrity.total,\n projectsHealthy: digest.nexusIntegrity.healthy,\n tempGcCandidates: digest.tempGc.candidates.length,\n duplicateEpicGroups: digest.duplicateEpics.groups.length,\n worktreesPruned: digest.worktreePrune.totalPruned,\n },\n });\n log.info({}, `hygiene complete: ${digest.summary}`);\n } catch (err) {\n log.error({ err }, 'hygiene error (caught at cron boundary)');\n }\n },\n { noOverlap: true, name: 'cleo-sentient-hygiene' },\n );\n\n return { lockPath, statePath, studioSupervisor, watcher, lock };\n },\n\n async healthProbe(): Promise<SubsystemHealth> {\n try {\n const state = await readSentientState(statePath);\n const running = state.pid\n ? (() => {\n try {\n process.kill(state.pid, 0);\n return true;\n } catch {\n return false;\n }\n })()\n : false;\n return {\n child_id: 'sentient',\n pid: running ? (state.pid ?? 0) : 0,\n state: running ? 'running' : 'stopped',\n restart_count: 0,\n detail: `killSwitch=${state.killSwitch} lastTickAt=${state.lastTickAt ?? 'never'}`,\n };\n } catch {\n return { child_id: 'sentient', pid: 0, state: 'stopped', restart_count: 0 };\n }\n },\n\n async shutdown(context: SentientContext): Promise<void> {\n // 1. Stop Studio.\n if (context.studioSupervisor !== null) {\n log.info({}, 'forwarding shutdown to Studio (SIGTERM)');\n try {\n await context.studioSupervisor.stop();\n } catch {\n // ignore\n }\n }\n // 2. Close state file watcher.\n try {\n context.watcher?.close();\n } catch {\n // ignore\n }\n // 3. Patch state — clear pid.\n try {\n await patchSentientState(context.statePath, {\n pid: null,\n killSwitchReason: 'subsystem shutdown',\n });\n } catch {\n // ignore\n }\n // 4. Release advisory lock.\n try {\n if (context.lock) await releaseLock(context.lock);\n } catch {\n // ignore\n }\n log.info({}, 'sentient subsystem stopped');\n },\n });\n}\n\n/**\n * Bootstrap the sentient daemon via a SubsystemRegistry.\n *\n * Alternative to `bootstrapDaemon` for host processes that want to drive the\n * sentient daemon through the uniform `start → healthProbe → shutdown`\n * lifecycle (e.g., a unified CLEO daemon host that also runs the GC subsystem\n * and the gateway subsystem). Installs SIGTERM/SIGINT handlers that call\n * `shutdownAll()` and keeps the process alive via node-cron.\n *\n * @param projectRoot - Absolute path to the project (contains `.cleo/`).\n * @param opts - Optional Studio supervision and config overrides.\n */\nexport async function bootstrapSentientRegistry(\n projectRoot: string,\n opts: BootstrapDaemonOptions = {},\n): Promise<void> {\n const registry = new SubsystemRegistry({\n onStart: (name: string) => log.info({ name }, `subsystem started: ${name}`),\n onShutdown: (name: string) => log.info({ name }, `subsystem shutdown: ${name}`),\n onError: (name: string, err: Error, phase: string) =>\n log.error({ name, phase, err }, `subsystem lifecycle error [${phase}]`),\n });\n\n registry.register(createSentientSubsystem(projectRoot, opts));\n await registry.startAll();\n\n const shutdown = async (reason: string): Promise<void> => {\n log.info({ reason }, 'shutdown signal received');\n await registry.shutdownAll();\n process.exit(0);\n };\n\n process.on('SIGTERM', () => {\n void shutdown('SIGTERM');\n });\n process.on('SIGINT', () => {\n void shutdown('SIGINT');\n });\n // Process stays alive via node-cron scheduler.\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","../src/sentient-subsystem.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\n// R4 (T11255) — sentient daemon subsystem adapter\nexport {\n bootstrapSentientRegistry,\n createSentientSubsystem,\n} from './sentient-subsystem.js';\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 or the base URL is a retired SignalDock host). */\n heartbeat: HeartbeatService | null;\n /** The KeyRotationService instance (null if disabled or the base URL is a retired SignalDock host). */\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 // A retired SignalDock base URL has no cloud to report presence to or\n // rotate keys with, so neither cloud service starts (T13169).\n const cloudRetired = conduit.isRetiredCloudUrl(credential.apiBaseUrl);\n\n // Heartbeat service (disabled when intervalMs is 0)\n let heartbeat: HeartbeatService | null = null;\n if (config?.heartbeatIntervalMs !== 0 && !cloudRetired) {\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 && !cloudRetired) {\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';\nimport { conduitFetch } from '@cleocode/core/conduit/cloud-endpoint.js';\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 conduitFetch(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 conduitFetch(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 agent's cloud 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 * A retired SignalDock host is refused before any network I/O (T13169).\n *\n * @task T218\n */\n\nimport { conduitFetch } from '@cleocode/core/conduit/cloud-endpoint.js';\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 conduitFetch(\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","/**\n * Sentient daemon subsystem adapter — R4 (T11255).\n *\n * Expresses the full sentient daemon lifecycle (Studio supervision, Tier-1\n * tick cron, Tier-2 proposal cron, skill curator cron, nightly hygiene loop)\n * as a single {@link Subsystem} via `defineSubsystem` from\n * `@cleocode/runtime/daemon`.\n *\n * ### Package placement\n * This file lives in `@cleocode/runtime` (NOT `@cleocode/core`) to respect the\n * dependency direction: `@cleocode/runtime` → `@cleocode/core` (runtime\n * consumes core; core must not import runtime to avoid a circular dep).\n *\n * ### Standalone-daemon path (daemon-entry.ts)\n * The legacy `bootstrapDaemon` in `@cleocode/core/sentient` continues to\n * function as the entry point for the standalone `cleo daemon start --foreground`\n * path. `createSentientSubsystem` is the adapter for hosts that want to embed\n * the sentient daemon inside a `SubsystemRegistry`-driven process.\n *\n * @packageDocumentation\n * @module @cleocode/runtime\n *\n * @epic T11255 R4 — migrate sentient/daemon.ts → daemon subsystem\n * @saga T11243 SG-RUNTIME-UNIFICATION\n */\n\nimport type { FSWatcher } from 'node:fs';\nimport { watch } from 'node:fs';\nimport { join } from 'node:path';\nimport type { Subsystem, SubsystemHealth } from '@cleocode/contracts';\nimport {\n acquireLock,\n type BootstrapDaemonOptions,\n curatorCronExpression,\n patchSentientState,\n readCuratorConfig,\n readSentientState,\n readSuperviseStudioConfig,\n releaseLock,\n reVerifyWorkerReport,\n SENTIENT_CRON_EXPR,\n SENTIENT_HYGIENE_CRON_EXPR,\n SENTIENT_LOCK_FILE,\n SENTIENT_PROPOSE_CRON_EXPR,\n SENTIENT_STATE_FILE,\n StudioSupervisor,\n safeRunCrossProjectHygiene,\n safeRunProposeTick,\n safeRunTick,\n warmupWorktreeBackend,\n} from '@cleocode/core/sentient';\nimport cron from 'node-cron';\nimport { createSubsystemLogger, defineSubsystem, SubsystemRegistry } from './daemon/index.js';\n\n// Internal logger for the subsystem.\nconst log = createSubsystemLogger('sentient');\n\n/**\n * Runtime context threaded from `start()` into `shutdown()`.\n *\n * Holds every handle the shutdown path needs to tear down cleanly.\n * Kept internal to this module — callers interact via `SubsystemRegistry`.\n */\ninterface SentientContext {\n readonly lockPath: string;\n readonly statePath: string;\n readonly studioSupervisor: StudioSupervisor | null;\n readonly watcher: FSWatcher | null;\n readonly lock: Awaited<ReturnType<typeof acquireLock>>;\n}\n\n/**\n * Declare the sentient daemon as a supervised daemon {@link Subsystem}.\n *\n * The returned subsystem (frozen by `defineSubsystem`) is registered with a\n * `SubsystemRegistry` in a host process. It carries the full lifecycle\n * that the legacy `bootstrapDaemon` ran inline:\n *\n * 1. `start()` — acquires the advisory lock, persists pid/startedAt,\n * optionally starts Studio supervision, watches the state file, and\n * schedules all four cron jobs (Tier-1, Tier-2, curator, hygiene). Runs\n * one boot tick before returning.\n * 2. `healthProbe()` — returns a `SubsystemHealth` row based on the pid\n * recorded in state.json.\n * 3. `shutdown()` — cascades Studio SIGTERM → watcher.close() → state patch\n * → lock release.\n *\n * @param projectRoot - Absolute path to the project (contains `.cleo/`).\n * @param opts - Optional overrides for Studio supervision and config path.\n * @returns A frozen {@link Subsystem} for `SubsystemRegistry.register()`.\n */\nexport function createSentientSubsystem(\n projectRoot: string,\n opts: BootstrapDaemonOptions = {},\n): Subsystem<SentientContext> {\n const statePath = join(projectRoot, SENTIENT_STATE_FILE);\n const lockPath = join(projectRoot, SENTIENT_LOCK_FILE);\n\n return defineSubsystem<SentientContext>({\n name: 'sentient',\n\n async start(): Promise<SentientContext> {\n // 1. Advisory lock.\n const lock = await acquireLock(lockPath);\n if (!lock) {\n log.error({}, 'lock acquisition failed — another daemon is running');\n process.exit(2);\n }\n\n // 2. Persist pid + startedAt.\n await patchSentientState(statePath, {\n pid: process.pid,\n startedAt: new Date().toISOString(),\n });\n\n // 3. Studio supervision.\n let studioSupervisor: StudioSupervisor | null = null;\n let shouldSuperviseStudio: boolean;\n if (typeof opts.superviseStudio === 'boolean') {\n shouldSuperviseStudio = opts.superviseStudio;\n } else {\n const { getCleoHome } = await import('@cleocode/core');\n const defaultConfigPath = join(getCleoHome(), 'config.json');\n const configPath = opts.globalConfigPath ?? defaultConfigPath;\n shouldSuperviseStudio = await readSuperviseStudioConfig(configPath);\n }\n\n if (shouldSuperviseStudio) {\n studioSupervisor = new StudioSupervisor(opts.studioOptions ?? {});\n try {\n studioSupervisor.start();\n log.info({}, 'Studio supervision enabled');\n } catch (err) {\n log.error({ err }, 'Studio supervision start failed (non-fatal; ticks continue)');\n studioSupervisor = null;\n }\n } else {\n log.info({}, 'Studio supervision disabled (daemon.superviseStudio=false)');\n }\n\n // 4. State file watcher.\n let watcher: FSWatcher | null = null;\n try {\n watcher = watch(statePath, { persistent: false }, () => {\n // Next tick will re-read state and honour kill-switch.\n });\n } catch {\n watcher = null;\n }\n\n // 5. Warm up worktree backend.\n await warmupWorktreeBackend();\n\n // 6. Boot tick (Tier-1 immediate run).\n // Wire reVerify (AC2/T11497) so the T1589 re-verify gate re-runs gates\n // on worker exit=0 instead of trusting the self-report.\n // Wire scope filter (AC1/T11497) for headless / walk-away autopilot.\n const tickOptions = {\n projectRoot,\n statePath,\n reVerify: reVerifyWorkerReport,\n scopeSagaId: opts.scopeSagaId,\n scopeEpicId: opts.scopeEpicId,\n };\n await patchSentientState(statePath, { lastCronFiredAt: new Date().toISOString() });\n const outcome = await safeRunTick(tickOptions);\n log.info(\n { kind: outcome.kind, taskId: outcome.taskId ?? null },\n `boot tick: ${outcome.kind} (task=${outcome.taskId ?? 'n/a'}) ${outcome.detail}`,\n );\n\n // 7. Schedule cron jobs.\n\n // Tier-1: every 5 minutes.\n cron.schedule(\n SENTIENT_CRON_EXPR,\n async () => {\n try {\n await patchSentientState(statePath, { lastCronFiredAt: new Date().toISOString() });\n } catch (err) {\n log.warn({ err }, 'heartbeat write failed');\n }\n try {\n const result = await safeRunTick(tickOptions);\n log.info(\n { kind: result.kind, taskId: result.taskId ?? null },\n `tick: ${result.kind} (task=${result.taskId ?? 'n/a'}) ${result.detail}`,\n );\n } catch (err) {\n log.error({ err }, 'tick error (caught at cron boundary)');\n }\n },\n { timezone: 'UTC', noOverlap: true, name: 'cleo-sentient' },\n );\n\n // Tier-2: every 2 hours.\n const proposeOptions = { projectRoot, statePath };\n cron.schedule(\n SENTIENT_PROPOSE_CRON_EXPR,\n async () => {\n try {\n const state = await readSentientState(statePath);\n if (!state.tier2Enabled) return;\n const result = await safeRunProposeTick(proposeOptions);\n log.info(\n { written: result.written, count: result.count },\n `propose: ${result.kind} ${result.detail}`,\n );\n } catch (err) {\n log.error({ err }, 'propose error (caught at cron boundary)');\n }\n },\n { timezone: 'UTC', noOverlap: true, name: 'cleo-sentient-propose' },\n );\n\n // Skill curator: opt-in, default off.\n {\n const { getCleoHome } = await import('@cleocode/core');\n const curatorConfigPath = opts.globalConfigPath ?? join(getCleoHome(), 'config.json');\n const curatorCfg = await readCuratorConfig(curatorConfigPath);\n if (curatorCfg.enabled) {\n const expr = curatorCronExpression(curatorCfg.runEveryHours);\n log.info({ interval: curatorCfg.runEveryHours, expr }, `curator enabled`);\n cron.schedule(\n expr,\n async () => {\n try {\n const curatorState = await readSentientState(statePath);\n if (curatorState.killSwitch) return;\n const { runCuratorTick } = await import('@cleocode/core/sentient');\n const result = await runCuratorTick({\n staleAfterDays: curatorCfg.staleAfterDays,\n archiveAfterDays: curatorCfg.archiveAfterDays,\n });\n log.info(\n { checked: result.summary.checked, stale: result.summary.markedStale },\n 'curator tick complete',\n );\n } catch (err) {\n log.error({ err }, 'curator error (caught at cron boundary)');\n }\n },\n { timezone: 'UTC', noOverlap: true, name: 'cleo-sentient-curator' },\n );\n } else {\n log.info({}, 'curator disabled (daemon.curator.enabled=false)');\n }\n }\n\n // Nightly hygiene: 02:00 UTC (or CLEO_HYGIENE_CRON override).\n cron.schedule(\n SENTIENT_HYGIENE_CRON_EXPR,\n async () => {\n try {\n const hygieneState = await readSentientState(statePath);\n if (hygieneState.killSwitch) return;\n log.info({}, 'nightly cross-project hygiene loop starting');\n const digest = await safeRunCrossProjectHygiene();\n await patchSentientState(statePath, {\n hygieneLastRunAt: digest.completedAt,\n hygieneSummary: digest.summary,\n hygieneStats: {\n projectsChecked: digest.nexusIntegrity.total,\n projectsHealthy: digest.nexusIntegrity.healthy,\n tempGcCandidates: digest.tempGc.candidates.length,\n duplicateEpicGroups: digest.duplicateEpics.groups.length,\n worktreesPruned: digest.worktreePrune.totalPruned,\n },\n });\n log.info({}, `hygiene complete: ${digest.summary}`);\n } catch (err) {\n log.error({ err }, 'hygiene error (caught at cron boundary)');\n }\n },\n { noOverlap: true, name: 'cleo-sentient-hygiene' },\n );\n\n return { lockPath, statePath, studioSupervisor, watcher, lock };\n },\n\n async healthProbe(): Promise<SubsystemHealth> {\n try {\n const state = await readSentientState(statePath);\n const running = state.pid\n ? (() => {\n try {\n process.kill(state.pid, 0);\n return true;\n } catch {\n return false;\n }\n })()\n : false;\n return {\n child_id: 'sentient',\n pid: running ? (state.pid ?? 0) : 0,\n state: running ? 'running' : 'stopped',\n restart_count: 0,\n detail: `killSwitch=${state.killSwitch} lastTickAt=${state.lastTickAt ?? 'never'}`,\n };\n } catch {\n return { child_id: 'sentient', pid: 0, state: 'stopped', restart_count: 0 };\n }\n },\n\n async shutdown(context: SentientContext): Promise<void> {\n // 1. Stop Studio.\n if (context.studioSupervisor !== null) {\n log.info({}, 'forwarding shutdown to Studio (SIGTERM)');\n try {\n await context.studioSupervisor.stop();\n } catch {\n // ignore\n }\n }\n // 2. Close state file watcher.\n try {\n context.watcher?.close();\n } catch {\n // ignore\n }\n // 3. Patch state — clear pid.\n try {\n await patchSentientState(context.statePath, {\n pid: null,\n killSwitchReason: 'subsystem shutdown',\n });\n } catch {\n // ignore\n }\n // 4. Release advisory lock.\n try {\n if (context.lock) await releaseLock(context.lock);\n } catch {\n // ignore\n }\n log.info({}, 'sentient subsystem stopped');\n },\n });\n}\n\n/**\n * Bootstrap the sentient daemon via a SubsystemRegistry.\n *\n * Alternative to `bootstrapDaemon` for host processes that want to drive the\n * sentient daemon through the uniform `start → healthProbe → shutdown`\n * lifecycle (e.g., a unified CLEO daemon host that also runs the GC subsystem\n * and the gateway subsystem). Installs SIGTERM/SIGINT handlers that call\n * `shutdownAll()` and keeps the process alive via node-cron.\n *\n * @param projectRoot - Absolute path to the project (contains `.cleo/`).\n * @param opts - Optional Studio supervision and config overrides.\n */\nexport async function bootstrapSentientRegistry(\n projectRoot: string,\n opts: BootstrapDaemonOptions = {},\n): Promise<void> {\n const registry = new SubsystemRegistry({\n onStart: (name: string) => log.info({ name }, `subsystem started: ${name}`),\n onShutdown: (name: string) => log.info({ name }, `subsystem shutdown: ${name}`),\n onError: (name: string, err: Error, phase: string) =>\n log.error({ name, phase, err }, `subsystem lifecycle error [${phase}]`),\n });\n\n registry.register(createSentientSubsystem(projectRoot, opts));\n await registry.startAll();\n\n const shutdown = async (reason: string): Promise<void> => {\n log.info({ reason }, 'shutdown signal received');\n await registry.shutdownAll();\n process.exit(0);\n };\n\n process.on('SIGTERM', () => {\n void shutdown('SIGTERM');\n });\n process.on('SIGINT', () => {\n void shutdown('SIGINT');\n });\n // Process stays alive via node-cron scheduler.\n}\n"],"mappings":";;;;;;;AAUA,SAAS,eAAe;;;ACCxB,SAAS,oBAAoB;AA4B7B,IAAM,wBAAwB;AAC9B,IAAM,2BAA2B;AAM1B,IAAM,cAAN,MAAkB;AAAA,EACf;AAAA,EACA,UAAiC;AAAA,EACjC,WAAkD;AAAA,EAClD,iBAAiB,oBAAI,IAAY;AAAA,EACjC,UAAU;AAAA,EAElB,YAAY,QAA2B;AACrC,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA,EAGA,UAAU,SAA+B;AACvC,SAAK,UAAU;AAAA,EACjB;AAAA;AAAA,EAGA,QAAc;AACZ,QAAI,KAAK,QAAS;AAClB,SAAK,UAAU;AAEf,UAAM,aAAa,KAAK,OAAO,kBAAkB;AAGjD,SAAK,KAAK,UAAU;AAEpB,SAAK,WAAW,YAAY,MAAM;AAChC,WAAK,KAAK,UAAU;AAAA,IACtB,GAAG,UAAU;AAAA,EACf;AAAA;AAAA,EAGA,OAAa;AACX,SAAK,UAAU;AACf,QAAI,KAAK,UAAU;AACjB,oBAAc,KAAK,QAAQ;AAC3B,WAAK,WAAW;AAAA,IAClB;AAAA,EACF;AAAA;AAAA,EAGA,SAAkD;AAChD,WAAO;AAAA,MACL,SAAS,KAAK;AAAA,MACd,WAAW,KAAK,eAAe;AAAA,IACjC;AAAA,EACF;AAAA;AAAA,EAGA,MAAc,YAA2B;AACvC,QAAI,CAAC,KAAK,QAAS;AAEnB,UAAM,cAAgC,CAAC;AAGvC,QAAI;AACF,YAAM,eAAe,MAAM,KAAK,aAAa;AAC7C,iBAAW,OAAO,cAAc;AAC9B,YAAI,CAAC,KAAK,eAAe,IAAI,IAAI,EAAE,GAAG;AACpC,eAAK,eAAe,IAAI,IAAI,EAAE;AAC9B,sBAAY,KAAK,GAAG;AAAA,QACtB;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AAGA,UAAM,WAAW,KAAK,OAAO,wBAAwB,CAAC;AACtD,eAAW,UAAU,UAAU;AAC7B,UAAI;AACF,cAAM,gBAAgB,MAAM,KAAK,sBAAsB,MAAM;AAC7D,mBAAW,OAAO,eAAe;AAC/B,cAAI,CAAC,KAAK,eAAe,IAAI,IAAI,EAAE,GAAG;AACpC,iBAAK,eAAe,IAAI,IAAI,EAAE;AAC9B,wBAAY,KAAK,GAAG;AAAA,UACtB;AAAA,QACF;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAGA,eAAW,OAAO,aAAa;AAC7B,WAAK,QAAQ,GAAG;AAAA,IAClB;AAGA,QAAI,KAAK,eAAe,OAAO,KAAM;AACnC,YAAM,UAAU,CAAC,GAAG,KAAK,cAAc;AACvC,WAAK,iBAAiB,IAAI,IAAI,QAAQ,MAAM,IAAK,CAAC;AAAA,IACpD;AAAA,EACF;AAAA;AAAA,EAGA,MAAc,eAA0C;AACtD,QAAI,KAAK,OAAO,WAAW;AACzB,aAAO,KAAK,OAAO,UAAU,KAAK,EAAE,OAAO,GAAG,CAAC;AAAA,IACjD;AAGA,UAAM,SAAS,IAAI,gBAAgB;AACnC,WAAO,IAAI,aAAa,KAAK,OAAO,OAAO;AAC3C,WAAO,IAAI,SAAS,IAAI;AAExB,UAAM,MAAM,GAAG,KAAK,OAAO,UAAU,kBAAkB,MAAM;AAC7D,UAAM,WAAW,MAAM,aAAa,KAAK;AAAA,MACvC,QAAQ;AAAA,MACR,SAAS,KAAK,QAAQ;AAAA,IACxB,CAAC;AAED,QAAI,CAAC,SAAS,GAAI,QAAO,CAAC;AAE1B,UAAM,OAAQ,MAAM,SAAS,KAAK;AAYlC,YAAQ,KAAK,MAAM,YAAY,CAAC,GAAG,IAAI,CAAC,OAAO;AAAA,MAC7C,IAAI,EAAE;AAAA,MACN,MAAM,EAAE,eAAe;AAAA,MACvB,SAAS,EAAE,WAAW;AAAA,MACtB,UAAU,EAAE;AAAA,MACZ,WAAW,EAAE,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,IACnD,EAAE;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,sBAAsB,gBAAmD;AACrF,UAAM,QAAQ,KAAK,OAAO,kBAAkB;AAC5C,UAAM,MAAM,GAAG,KAAK,OAAO,UAAU,kBAAkB,cAAc,6BAA6B,KAAK;AAEvG,UAAM,WAAW,MAAM,aAAa,KAAK;AAAA,MACvC,QAAQ;AAAA,MACR,SAAS,KAAK,QAAQ;AAAA,IACxB,CAAC;AAED,QAAI,CAAC,SAAS,GAAI,QAAO,CAAC;AAE1B,UAAM,OAAQ,MAAM,SAAS,KAAK;AAYlC,UAAM,iBAAiB,IAAI,OAAO,IAAI,KAAK,OAAO,OAAO,eAAe,GAAG;AAE3E,YAAQ,KAAK,MAAM,YAAY,CAAC,GAC7B,OAAO,CAAC,MAAM;AAEb,YAAM,UAAU,EAAE,WAAW;AAC7B,aAAO,eAAe,KAAK,OAAO,KAAK,EAAE,gBAAgB,KAAK,OAAO;AAAA,IACvE,CAAC,EACA,IAAI,CAAC,OAAO;AAAA,MACX,IAAI,EAAE;AAAA,MACN,MAAM,EAAE,eAAe;AAAA,MACvB,SAAS,EAAE,WAAW;AAAA,MACtB,UAAU,EAAE,kBAAkB;AAAA,MAC9B,WAAW,EAAE,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,IACnD,EAAE;AAAA,EACN;AAAA;AAAA,EAGQ,UAAkC;AACxC,WAAO;AAAA,MACL,gBAAgB;AAAA,MAChB,eAAe,UAAU,KAAK,OAAO,MAAM;AAAA,MAC3C,cAAc,KAAK,OAAO;AAAA,IAC5B;AAAA,EACF;AACF;;;AC9NA,SAAS,gBAAAA,qBAAoB;AAe7B,IAAM,sBAAsB;AAGrB,IAAM,mBAAN,MAAuB;AAAA,EACpB;AAAA,EACA,QAA+C;AAAA,EAC/C,UAAU;AAAA,EACV,sBAAsB;AAAA,EAE9B,YAAY,QAAyB;AACnC,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA,EAGA,QAAc;AACZ,QAAI,KAAK,QAAS;AAClB,SAAK,UAAU;AACf,SAAK,sBAAsB;AAE3B,UAAM,aAAa,KAAK,OAAO,cAAc;AAG7C,SAAK,KAAK,cAAc;AAExB,SAAK,QAAQ,YAAY,MAAM;AAC7B,WAAK,KAAK,cAAc;AAAA,IAC1B,GAAG,UAAU;AAAA,EACf;AAAA;AAAA,EAGA,OAAa;AACX,SAAK,UAAU;AACf,QAAI,KAAK,OAAO;AACd,oBAAc,KAAK,KAAK;AACxB,WAAK,QAAQ;AAAA,IACf;AAAA,EACF;AAAA;AAAA,EAGA,SAA4D;AAC1D,WAAO;AAAA,MACL,SAAS,KAAK;AAAA,MACd,qBAAqB,KAAK;AAAA,IAC5B;AAAA,EACF;AAAA;AAAA,EAGA,MAAc,gBAA+B;AAC3C,QAAI;AACF,YAAM,WAAW,MAAMA;AAAA,QACrB,GAAG,KAAK,OAAO,UAAU,WAAW,KAAK,OAAO,OAAO;AAAA,QACvD;AAAA,UACE,QAAQ;AAAA,UACR,SAAS;AAAA,YACP,gBAAgB;AAAA,YAChB,eAAe,UAAU,KAAK,OAAO,MAAM;AAAA,YAC3C,cAAc,KAAK,OAAO;AAAA,UAC5B;AAAA,UACA,MAAM,KAAK,UAAU,EAAE,QAAQ,SAAS,CAAC;AAAA,UACzC,QAAQ,YAAY,QAAQ,GAAM;AAAA,QACpC;AAAA,MACF;AAEA,UAAI,SAAS,IAAI;AACf,aAAK,sBAAsB;AAAA,MAC7B,OAAO;AACL,aAAK;AAAA,MACP;AAAA,IACF,QAAQ;AACN,WAAK;AAAA,IACP;AAAA,EACF;AACF;;;ACxEA,IAAM,4BAA4B;AAGlC,IAAM,yBAAyB,KAAK,KAAK,KAAK,KAAK;AAG5C,IAAM,qBAAN,MAAyB;AAAA,EACtB;AAAA,EACA,QAA+C;AAAA,EAC/C,UAAU;AAAA,EACV,iBAAgC;AAAA,EAExC,YAAY,QAA2B;AACrC,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA,EAGA,QAAc;AACZ,QAAI,KAAK,QAAS;AAClB,SAAK,UAAU;AAEf,UAAM,aAAa,KAAK,OAAO,mBAAmB;AAGlD,eAAW,MAAM;AACf,WAAK,KAAK,eAAe;AAAA,IAC3B,GAAG,GAAI;AAEP,SAAK,QAAQ,YAAY,MAAM;AAC7B,WAAK,KAAK,eAAe;AAAA,IAC3B,GAAG,UAAU;AAAA,EACf;AAAA;AAAA,EAGA,OAAa;AACX,SAAK,UAAU;AACf,QAAI,KAAK,OAAO;AACd,oBAAc,KAAK,KAAK;AACxB,WAAK,QAAQ;AAAA,IACf;AAAA,EACF;AAAA;AAAA,EAGA,SAA8D;AAC5D,WAAO;AAAA,MACL,SAAS,KAAK;AAAA,MACd,gBAAgB,KAAK;AAAA,IACvB;AAAA,EACF;AAAA;AAAA,EAGA,MAAc,iBAAgC;AAC5C,QAAI;AACF,YAAM,aAAa,MAAM,KAAK,OAAO,SAAS,IAAI,KAAK,OAAO,OAAO;AACrE,UAAI,CAAC,WAAY;AAEjB,YAAM,SAAS,KAAK,OAAO,eAAe;AAC1C,YAAM,gBAAgB,KAAK,IAAI,IAAI,IAAI,KAAK,WAAW,SAAS,EAAE,QAAQ;AAE1E,UAAI,gBAAgB,QAAQ;AAC1B,cAAM,KAAK,OAAO,SAAS,UAAU,KAAK,OAAO,OAAO;AACxD,aAAK,kBAAiB,oBAAI,KAAK,GAAE,YAAY;AAAA,MAC/C;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AACF;;;AC3DO,IAAM,uBAAN,MAA2B;AAAA,EACxB;AAAA,EACA,UAAoC;AAAA,EACpC,cAAmC;AAAA,EACnC,UAAU;AAAA,EAElB,YAAY,QAA6B;AACvC,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA,EAGA,UAAU,SAAkC;AAC1C,SAAK,UAAU;AAAA,EACjB;AAAA;AAAA,EAGA,MAAM,QAAuB;AAC3B,QAAI,KAAK,QAAS;AAClB,SAAK,UAAU;AAEf,UAAM,KAAK,OAAO,UAAU,QAAQ;AAAA,MAClC,SAAS,KAAK,OAAO;AAAA,MACrB,QAAQ,KAAK,OAAO;AAAA,MACpB,YAAY,KAAK,OAAO;AAAA,MACxB,aAAa,KAAK,OAAO;AAAA,IAC3B,CAAC;AAGD,QAAI,KAAK,WAAW,KAAK,OAAO,UAAU,WAAW;AACnD,WAAK,cAAc,KAAK,OAAO,UAAU,UAAU,KAAK,OAAO;AAAA,IACjE;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,OAAsB;AAC1B,SAAK,UAAU;AAEf,QAAI,KAAK,aAAa;AACpB,WAAK,YAAY;AACjB,WAAK,cAAc;AAAA,IACrB;AAEA,UAAM,KAAK,OAAO,UAAU,WAAW;AAAA,EACzC;AAAA;AAAA,EAGA,SAAsD;AACpD,WAAO;AAAA,MACL,SAAS,KAAK;AAAA,MACd,eAAe,KAAK,OAAO,UAAU;AAAA,IACvC;AAAA,EACF;AACF;;;AC3DA,SAAS,aAAa;AACtB,SAAS,YAAY;AAErB;AAAA,EACE;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,OAAO,UAAU;AAIjB,IAAM,MAAM,sBAAsB,UAAU;AAoCrC,SAAS,wBACd,aACA,OAA+B,CAAC,GACJ;AAC5B,QAAM,YAAY,KAAK,aAAa,mBAAmB;AACvD,QAAM,WAAW,KAAK,aAAa,kBAAkB;AAErD,SAAO,gBAAiC;AAAA,IACtC,MAAM;AAAA,IAEN,MAAM,QAAkC;AAEtC,YAAM,OAAO,MAAM,YAAY,QAAQ;AACvC,UAAI,CAAC,MAAM;AACT,YAAI,MAAM,CAAC,GAAG,0DAAqD;AACnE,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAGA,YAAM,mBAAmB,WAAW;AAAA,QAClC,KAAK,QAAQ;AAAA,QACb,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MACpC,CAAC;AAGD,UAAI,mBAA4C;AAChD,UAAI;AACJ,UAAI,OAAO,KAAK,oBAAoB,WAAW;AAC7C,gCAAwB,KAAK;AAAA,MAC/B,OAAO;AACL,cAAM,EAAE,YAAY,IAAI,MAAM,OAAO,gBAAgB;AACrD,cAAM,oBAAoB,KAAK,YAAY,GAAG,aAAa;AAC3D,cAAM,aAAa,KAAK,oBAAoB;AAC5C,gCAAwB,MAAM,0BAA0B,UAAU;AAAA,MACpE;AAEA,UAAI,uBAAuB;AACzB,2BAAmB,IAAI,iBAAiB,KAAK,iBAAiB,CAAC,CAAC;AAChE,YAAI;AACF,2BAAiB,MAAM;AACvB,cAAI,KAAK,CAAC,GAAG,4BAA4B;AAAA,QAC3C,SAAS,KAAK;AACZ,cAAI,MAAM,EAAE,IAAI,GAAG,6DAA6D;AAChF,6BAAmB;AAAA,QACrB;AAAA,MACF,OAAO;AACL,YAAI,KAAK,CAAC,GAAG,4DAA4D;AAAA,MAC3E;AAGA,UAAI,UAA4B;AAChC,UAAI;AACF,kBAAU,MAAM,WAAW,EAAE,YAAY,MAAM,GAAG,MAAM;AAAA,QAExD,CAAC;AAAA,MACH,QAAQ;AACN,kBAAU;AAAA,MACZ;AAGA,YAAM,sBAAsB;AAM5B,YAAM,cAAc;AAAA,QAClB;AAAA,QACA;AAAA,QACA,UAAU;AAAA,QACV,aAAa,KAAK;AAAA,QAClB,aAAa,KAAK;AAAA,MACpB;AACA,YAAM,mBAAmB,WAAW,EAAE,kBAAiB,oBAAI,KAAK,GAAE,YAAY,EAAE,CAAC;AACjF,YAAM,UAAU,MAAM,YAAY,WAAW;AAC7C,UAAI;AAAA,QACF,EAAE,MAAM,QAAQ,MAAM,QAAQ,QAAQ,UAAU,KAAK;AAAA,QACrD,cAAc,QAAQ,IAAI,UAAU,QAAQ,UAAU,KAAK,KAAK,QAAQ,MAAM;AAAA,MAChF;AAKA,WAAK;AAAA,QACH;AAAA,QACA,YAAY;AACV,cAAI;AACF,kBAAM,mBAAmB,WAAW,EAAE,kBAAiB,oBAAI,KAAK,GAAE,YAAY,EAAE,CAAC;AAAA,UACnF,SAAS,KAAK;AACZ,gBAAI,KAAK,EAAE,IAAI,GAAG,wBAAwB;AAAA,UAC5C;AACA,cAAI;AACF,kBAAM,SAAS,MAAM,YAAY,WAAW;AAC5C,gBAAI;AAAA,cACF,EAAE,MAAM,OAAO,MAAM,QAAQ,OAAO,UAAU,KAAK;AAAA,cACnD,SAAS,OAAO,IAAI,UAAU,OAAO,UAAU,KAAK,KAAK,OAAO,MAAM;AAAA,YACxE;AAAA,UACF,SAAS,KAAK;AACZ,gBAAI,MAAM,EAAE,IAAI,GAAG,sCAAsC;AAAA,UAC3D;AAAA,QACF;AAAA,QACA,EAAE,UAAU,OAAO,WAAW,MAAM,MAAM,gBAAgB;AAAA,MAC5D;AAGA,YAAM,iBAAiB,EAAE,aAAa,UAAU;AAChD,WAAK;AAAA,QACH;AAAA,QACA,YAAY;AACV,cAAI;AACF,kBAAM,QAAQ,MAAM,kBAAkB,SAAS;AAC/C,gBAAI,CAAC,MAAM,aAAc;AACzB,kBAAM,SAAS,MAAM,mBAAmB,cAAc;AACtD,gBAAI;AAAA,cACF,EAAE,SAAS,OAAO,SAAS,OAAO,OAAO,MAAM;AAAA,cAC/C,YAAY,OAAO,IAAI,IAAI,OAAO,MAAM;AAAA,YAC1C;AAAA,UACF,SAAS,KAAK;AACZ,gBAAI,MAAM,EAAE,IAAI,GAAG,yCAAyC;AAAA,UAC9D;AAAA,QACF;AAAA,QACA,EAAE,UAAU,OAAO,WAAW,MAAM,MAAM,wBAAwB;AAAA,MACpE;AAGA;AACE,cAAM,EAAE,YAAY,IAAI,MAAM,OAAO,gBAAgB;AACrD,cAAM,oBAAoB,KAAK,oBAAoB,KAAK,YAAY,GAAG,aAAa;AACpF,cAAM,aAAa,MAAM,kBAAkB,iBAAiB;AAC5D,YAAI,WAAW,SAAS;AACtB,gBAAM,OAAO,sBAAsB,WAAW,aAAa;AAC3D,cAAI,KAAK,EAAE,UAAU,WAAW,eAAe,KAAK,GAAG,iBAAiB;AACxE,eAAK;AAAA,YACH;AAAA,YACA,YAAY;AACV,kBAAI;AACF,sBAAM,eAAe,MAAM,kBAAkB,SAAS;AACtD,oBAAI,aAAa,WAAY;AAC7B,sBAAM,EAAE,eAAe,IAAI,MAAM,OAAO,yBAAyB;AACjE,sBAAM,SAAS,MAAM,eAAe;AAAA,kBAClC,gBAAgB,WAAW;AAAA,kBAC3B,kBAAkB,WAAW;AAAA,gBAC/B,CAAC;AACD,oBAAI;AAAA,kBACF,EAAE,SAAS,OAAO,QAAQ,SAAS,OAAO,OAAO,QAAQ,YAAY;AAAA,kBACrE;AAAA,gBACF;AAAA,cACF,SAAS,KAAK;AACZ,oBAAI,MAAM,EAAE,IAAI,GAAG,yCAAyC;AAAA,cAC9D;AAAA,YACF;AAAA,YACA,EAAE,UAAU,OAAO,WAAW,MAAM,MAAM,wBAAwB;AAAA,UACpE;AAAA,QACF,OAAO;AACL,cAAI,KAAK,CAAC,GAAG,iDAAiD;AAAA,QAChE;AAAA,MACF;AAGA,WAAK;AAAA,QACH;AAAA,QACA,YAAY;AACV,cAAI;AACF,kBAAM,eAAe,MAAM,kBAAkB,SAAS;AACtD,gBAAI,aAAa,WAAY;AAC7B,gBAAI,KAAK,CAAC,GAAG,6CAA6C;AAC1D,kBAAM,SAAS,MAAM,2BAA2B;AAChD,kBAAM,mBAAmB,WAAW;AAAA,cAClC,kBAAkB,OAAO;AAAA,cACzB,gBAAgB,OAAO;AAAA,cACvB,cAAc;AAAA,gBACZ,iBAAiB,OAAO,eAAe;AAAA,gBACvC,iBAAiB,OAAO,eAAe;AAAA,gBACvC,kBAAkB,OAAO,OAAO,WAAW;AAAA,gBAC3C,qBAAqB,OAAO,eAAe,OAAO;AAAA,gBAClD,iBAAiB,OAAO,cAAc;AAAA,cACxC;AAAA,YACF,CAAC;AACD,gBAAI,KAAK,CAAC,GAAG,qBAAqB,OAAO,OAAO,EAAE;AAAA,UACpD,SAAS,KAAK;AACZ,gBAAI,MAAM,EAAE,IAAI,GAAG,yCAAyC;AAAA,UAC9D;AAAA,QACF;AAAA,QACA,EAAE,WAAW,MAAM,MAAM,wBAAwB;AAAA,MACnD;AAEA,aAAO,EAAE,UAAU,WAAW,kBAAkB,SAAS,KAAK;AAAA,IAChE;AAAA,IAEA,MAAM,cAAwC;AAC5C,UAAI;AACF,cAAM,QAAQ,MAAM,kBAAkB,SAAS;AAC/C,cAAM,UAAU,MAAM,OACjB,MAAM;AACL,cAAI;AACF,oBAAQ,KAAK,MAAM,KAAK,CAAC;AACzB,mBAAO;AAAA,UACT,QAAQ;AACN,mBAAO;AAAA,UACT;AAAA,QACF,GAAG,IACH;AACJ,eAAO;AAAA,UACL,UAAU;AAAA,UACV,KAAK,UAAW,MAAM,OAAO,IAAK;AAAA,UAClC,OAAO,UAAU,YAAY;AAAA,UAC7B,eAAe;AAAA,UACf,QAAQ,cAAc,MAAM,UAAU,eAAe,MAAM,cAAc,OAAO;AAAA,QAClF;AAAA,MACF,QAAQ;AACN,eAAO,EAAE,UAAU,YAAY,KAAK,GAAG,OAAO,WAAW,eAAe,EAAE;AAAA,MAC5E;AAAA,IACF;AAAA,IAEA,MAAM,SAAS,SAAyC;AAEtD,UAAI,QAAQ,qBAAqB,MAAM;AACrC,YAAI,KAAK,CAAC,GAAG,yCAAyC;AACtD,YAAI;AACF,gBAAM,QAAQ,iBAAiB,KAAK;AAAA,QACtC,QAAQ;AAAA,QAER;AAAA,MACF;AAEA,UAAI;AACF,gBAAQ,SAAS,MAAM;AAAA,MACzB,QAAQ;AAAA,MAER;AAEA,UAAI;AACF,cAAM,mBAAmB,QAAQ,WAAW;AAAA,UAC1C,KAAK;AAAA,UACL,kBAAkB;AAAA,QACpB,CAAC;AAAA,MACH,QAAQ;AAAA,MAER;AAEA,UAAI;AACF,YAAI,QAAQ,KAAM,OAAM,YAAY,QAAQ,IAAI;AAAA,MAClD,QAAQ;AAAA,MAER;AACA,UAAI,KAAK,CAAC,GAAG,4BAA4B;AAAA,IAC3C;AAAA,EACF,CAAC;AACH;AAcA,eAAsB,0BACpB,aACA,OAA+B,CAAC,GACjB;AACf,QAAM,WAAW,IAAI,kBAAkB;AAAA,IACrC,SAAS,CAAC,SAAiB,IAAI,KAAK,EAAE,KAAK,GAAG,sBAAsB,IAAI,EAAE;AAAA,IAC1E,YAAY,CAAC,SAAiB,IAAI,KAAK,EAAE,KAAK,GAAG,uBAAuB,IAAI,EAAE;AAAA,IAC9E,SAAS,CAAC,MAAc,KAAY,UAClC,IAAI,MAAM,EAAE,MAAM,OAAO,IAAI,GAAG,8BAA8B,KAAK,GAAG;AAAA,EAC1E,CAAC;AAED,WAAS,SAAS,wBAAwB,aAAa,IAAI,CAAC;AAC5D,QAAM,SAAS,SAAS;AAExB,QAAM,WAAW,OAAO,WAAkC;AACxD,QAAI,KAAK,EAAE,OAAO,GAAG,0BAA0B;AAC/C,UAAM,SAAS,YAAY;AAC3B,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,UAAQ,GAAG,WAAW,MAAM;AAC1B,SAAK,SAAS,SAAS;AAAA,EACzB,CAAC;AACD,UAAQ,GAAG,UAAU,MAAM;AACzB,SAAK,SAAS,QAAQ;AAAA,EACxB,CAAC;AAEH;;;ALhXA,IAAM,EAAE,iBAAiB,IAAI;AA2E7B,eAAsB,cACpB,UACA,QACwB;AACxB,QAAM,aAAa,QAAQ,UACvB,MAAM,SAAS,IAAI,OAAO,OAAO,IACjC,MAAM,SAAS,UAAU;AAE7B,MAAI,CAAC,YAAY;AACf,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAGA,QAAM,YAAY,QAAQ,aAAa,iBAAiB,UAAU;AAClE,QAAM,UAAU,QAAQ;AAAA,IACtB,SAAS,WAAW;AAAA,IACpB,QAAQ,WAAW;AAAA,IACnB,YAAY,WAAW;AAAA,IACvB,GAAG,WAAW;AAAA,EAChB,CAAC;AAED,QAAM,eAAkC;AAAA,IACtC,SAAS,WAAW;AAAA,IACpB,QAAQ,WAAW;AAAA,IACnB,YAAY,WAAW;AAAA,IACvB,gBAAgB,QAAQ,kBAAkB,WAAW,gBAAgB,kBAAkB;AAAA,IACvF,sBAAsB,QAAQ;AAAA,IAC9B,gBAAgB,QAAQ;AAAA,IACxB;AAAA,EACF;AAEA,QAAM,SAAS,IAAI,YAAY,YAAY;AAI3C,QAAM,eAAe,QAAQ,kBAAkB,WAAW,UAAU;AAGpE,MAAI,YAAqC;AACzC,MAAI,QAAQ,wBAAwB,KAAK,CAAC,cAAc;AACtD,gBAAY,IAAI,iBAAiB;AAAA,MAC/B,SAAS,WAAW;AAAA,MACpB,QAAQ,WAAW;AAAA,MACnB,YAAY,WAAW;AAAA,MACvB,YAAY,QAAQ;AAAA,IACtB,CAAC;AACD,cAAU,MAAM;AAAA,EAClB;AAGA,MAAI,cAAyC;AAC7C,MAAI,QAAQ,gBAAgB,KAAK,CAAC,cAAc;AAC9C,kBAAc,IAAI,mBAAmB;AAAA,MACnC,SAAS,WAAW;AAAA,MACpB;AAAA,MACA,aAAa,QAAQ;AAAA,IACvB,CAAC;AACD,gBAAY,MAAM;AAAA,EACpB;AAGA,MAAI,gBAA6C;AACjD,QAAM,cAAc,QAAQ,eAAe,WAAW,gBAAgB;AACtE,MAAI,eAAe,QAAQ,oBAAoB;AAC7C,oBAAgB,IAAI,qBAAqB;AAAA,MACvC,SAAS,WAAW;AAAA,MACpB,QAAQ,WAAW;AAAA,MACnB,YAAY,WAAW;AAAA,MACvB;AAAA,MACA,WAAW,OAAO,mBAAmB;AAAA,IACvC,CAAC;AAED,SAAK,cAAc,MAAM;AAAA,EAC3B;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,WAAW;AAAA,IACpB,MAAM,MAAM;AACV,aAAO,KAAK;AACZ,iBAAW,KAAK;AAChB,mBAAa,KAAK;AAClB,WAAK,eAAe,KAAK;AACzB,WAAK,UAAU,WAAW;AAAA,IAC5B;AAAA,EACF;AACF;","names":["conduitFetch"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cleocode/runtime",
3
- "version": "2026.10.4",
3
+ "version": "2026.10.5-rc.ci.672",
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",
@@ -18,6 +18,10 @@
18
18
  "types": "./dist/gateway/index.d.ts",
19
19
  "import": "./dist/gateway/index.js"
20
20
  },
21
+ "./gateway/dispatch": {
22
+ "types": "./dist/gateway/dispatch.d.ts",
23
+ "import": "./dist/gateway/dispatch.js"
24
+ },
21
25
  "./gateway/mcp": {
22
26
  "types": "./dist/gateway/mcp/index.d.ts",
23
27
  "import": "./dist/gateway/mcp/index.js"
@@ -33,8 +37,8 @@
33
37
  },
34
38
  "dependencies": {
35
39
  "node-cron": "^4.2.1",
36
- "@cleocode/contracts": "2026.10.4",
37
- "@cleocode/core": "2026.10.4"
40
+ "@cleocode/contracts": "2026.10.5-rc.ci.672",
41
+ "@cleocode/core": "2026.10.5-rc.ci.672"
38
42
  },
39
43
  "devDependencies": {
40
44
  "tsup": "^8.0.0",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/daemon/define-subsystem.ts","../src/daemon/lease-ipc-client.ts","../src/daemon/heartbeat-sender.ts","../src/daemon/logger.ts","../src/daemon/registry.ts","../src/daemon/supervisor-client.ts"],"sourcesContent":["/**\n * `defineSubsystem()` — declare a supervised daemon subsystem.\n *\n * The single factory R4-R7 adapters call to express a long-running concern\n * (Studio supervision, GC cron, web server, docs-viewer, runtime poller) as a\n * uniform {@link Subsystem}. The returned descriptor is frozen so the registry\n * can drive `start → healthProbe → shutdown` without the definition mutating\n * underfoot.\n *\n * @packageDocumentation\n * @module @cleocode/runtime/daemon\n *\n * @epic T11253 R2 — `@cleocode/runtime/daemon` submodule\n * @task T11367 — defineSubsystem + lifecycle/health registry + IPC client\n * @saga T11243 SG-RUNTIME-UNIFICATION\n */\n\nimport type { Subsystem, SubsystemDefinition } from '@cleocode/contracts';\n\n/**\n * Declare a daemon subsystem.\n *\n * Validates that the four required surfaces (`name`, `start`, `healthProbe`,\n * `shutdown`) are present and that `name` is a non-empty string, then returns a\n * frozen {@link Subsystem} ready to register with a `SubsystemRegistry`.\n *\n * @typeParam TContext - Opaque context threaded from `start` into `shutdown`\n * (e.g. a child handle or resolved config). Defaults to `void`.\n * @param definition - The subsystem descriptor.\n * @returns A frozen, registrable {@link Subsystem}.\n * @throws {TypeError} When `name` is empty or a required method is missing.\n *\n * @example\n * ```ts\n * const studio = defineSubsystem({\n * name: 'studio',\n * start: () => supervisor.start(),\n * healthProbe: () => ({ child_id: 'studio', pid: supervisor.pid ?? 0,\n * state: supervisor.status === 'running' ? 'running' : 'stopped',\n * restart_count: 0 }),\n * shutdown: () => supervisor.stop(),\n * });\n * ```\n */\nexport function defineSubsystem<TContext = void>(\n definition: SubsystemDefinition<TContext>,\n): Subsystem<TContext> {\n if (typeof definition.name !== 'string' || definition.name.length === 0) {\n throw new TypeError('defineSubsystem: `name` must be a non-empty string');\n }\n if (typeof definition.start !== 'function') {\n throw new TypeError(`defineSubsystem(${definition.name}): \\`start\\` must be a function`);\n }\n if (typeof definition.healthProbe !== 'function') {\n throw new TypeError(`defineSubsystem(${definition.name}): \\`healthProbe\\` must be a function`);\n }\n if (typeof definition.shutdown !== 'function') {\n throw new TypeError(`defineSubsystem(${definition.name}): \\`shutdown\\` must be a function`);\n }\n\n return Object.freeze({\n name: definition.name,\n start: definition.start,\n healthProbe: definition.healthProbe,\n shutdown: definition.shutdown,\n });\n}\n","/**\n * Typed NDJSON client for the PARALLEL `lease-ipc` v1.1 contract (T11627 ST-5).\n *\n * Mirrors {@link ./supervisor-client.ts | supervisor-client.ts} conventions\n * exactly — a transport-agnostic codec that **owns no socket**. It encodes\n * outbound {@link LeaseIpcRequestEnvelope}s to newline-delimited JSON (NDJSON)\n * lines and decodes inbound lines through the v1.1 Zod schemas. **Every inbound\n * line is parsed** — a malformed or schema-violating frame is rejected with a\n * typed {@link MalformedLeaseIpcFrameError} and never silently dropped.\n *\n * The wire format is byte-identical framing to the frozen `supervisor-ipc` v1.0\n * envelope; only the version string ({@link LEASE_IPC_PROTOCOL_VERSION} =\n * `1.2.0`) and the inner union differ. A single supervisor accept loop routes\n * `1.0.0` frames to the v1.0 union and `1.2.0` frames to this lease union.\n *\n * The Rust `cleo-supervisor` is the arbiter (server); a CLEO process running in\n * `CLEO_WRITER_LEASE_MODE=supervisor` is a client that sends lease requests and\n * receives lease responses + unsolicited events (`lease_revoked`,\n * `child_killed_unresponsive`), one NDJSON envelope per line.\n *\n * @packageDocumentation\n * @module @cleocode/runtime/daemon\n *\n * @epic T11625\n * @task T11894 — supervisor fast path: TS lease-ipc-client (ST-5)\n * @saga T11243 SG-RUNTIME-UNIFICATION\n * @see ./supervisor-client.ts — the v1.0 codec this mirrors\n */\n\nimport {\n LEASE_IPC_PROTOCOL_VERSION,\n type LeaseIpcRequest,\n type LeaseIpcRequestEnvelope,\n LeaseIpcRequestEnvelopeSchema,\n type LeaseIpcResponseEnvelope,\n LeaseIpcResponseEnvelopeSchema,\n} from '@cleocode/contracts';\n\n/**\n * A typed error raised when an inbound NDJSON line cannot be decoded into a\n * valid {@link LeaseIpcResponseEnvelope}.\n *\n * Carries the offending raw `line` and the underlying `cause` (a JSON\n * `SyntaxError` or a Zod validation error) so callers can log + correlate.\n * Invalid frames are surfaced as this error — never silently dropped. This is\n * the v1.1 sibling of {@link MalformedIpcFrameError}.\n *\n * @public\n */\nexport class MalformedLeaseIpcFrameError extends Error {\n /** The raw NDJSON line that failed to decode. */\n readonly line: string;\n\n /**\n * @param line - The offending raw NDJSON line.\n * @param cause - The underlying JSON or Zod error.\n */\n constructor(line: string, cause: unknown) {\n super(`Malformed lease-ipc frame: ${cause instanceof Error ? cause.message : cause}`);\n this.name = 'MalformedLeaseIpcFrameError';\n this.line = line;\n this.cause = cause;\n }\n}\n\n/**\n * A transport-agnostic codec for the PARALLEL `lease-ipc` v1.1 wire format.\n *\n * Construct via {@link createLeaseIpcClient}. The client is stateful only in its\n * monotonic correlation-id counter; it owns no socket. Callers feed it raw lines\n * from whatever transport (Unix socket, named pipe, test pair) they own.\n */\nexport interface LeaseIpcClient {\n /**\n * Encode a request body into a complete, newline-terminated NDJSON envelope\n * line ready to write to the transport.\n *\n * Stamps the parallel protocol version ({@link LEASE_IPC_PROTOCOL_VERSION}) and\n * a fresh correlation `id`, validates the result against\n * {@link LeaseIpcRequestEnvelopeSchema}, and returns the serialized line\n * (including the trailing `\\n`).\n *\n * @param request - The request body (validated by the envelope schema).\n * @returns `{ id, line }` — the correlation id and the NDJSON line to send.\n */\n encodeRequest: (request: LeaseIpcRequest) => { id: string; line: string };\n\n /**\n * Decode a single inbound NDJSON line into a typed response envelope.\n *\n * Parses the line as JSON, then validates it against\n * {@link LeaseIpcResponseEnvelopeSchema}. A malformed or schema-violating line\n * throws {@link MalformedLeaseIpcFrameError} — it is never silently dropped.\n *\n * @param line - A single NDJSON line (without the trailing newline).\n * @returns The decoded {@link LeaseIpcResponseEnvelope}.\n * @throws {MalformedLeaseIpcFrameError} When the line is not a valid response frame.\n */\n decodeResponseLine: (line: string) => LeaseIpcResponseEnvelope;\n}\n\n/**\n * Generate a process-unique correlation id for a lease IPC request envelope.\n *\n * @param seq - The client's monotonic sequence number.\n * @returns A correlation id of the form `l<seq>-<random>`.\n */\nfunction nextCorrelationId(seq: number): string {\n return `l${seq}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\n/**\n * Create a typed NDJSON codec for the PARALLEL `lease-ipc` v1.1 contract.\n *\n * @returns A {@link LeaseIpcClient}.\n *\n * @example\n * ```ts\n * const client = createLeaseIpcClient();\n * const { id, line } = client.encodeRequest({\n * kind: 'lease_acquire', scope: 'project', lane: 'tasks',\n * holder_id: 'pid-42:tasks', priority: 0, ttl_ms: 30_000, reentrant: true,\n * });\n * socket.write(line);\n * socket.on('line', (raw) => {\n * const env = client.decodeResponseLine(raw); // throws on a bad frame\n * if (env.response.kind === 'lease_granted') { /* ... *\\/ }\n * });\n * ```\n */\nexport function createLeaseIpcClient(): LeaseIpcClient {\n let seq = 0;\n\n return {\n encodeRequest(request: LeaseIpcRequest): { id: string; line: string } {\n seq += 1;\n const id = nextCorrelationId(seq);\n const envelope: LeaseIpcRequestEnvelope = {\n protocol_version: LEASE_IPC_PROTOCOL_VERSION,\n id,\n direction: 'request',\n request,\n };\n // Validate before serialization so we never emit an off-contract frame.\n const parsed = LeaseIpcRequestEnvelopeSchema.parse(envelope);\n return { id, line: `${JSON.stringify(parsed)}\\n` };\n },\n\n decodeResponseLine(line: string): LeaseIpcResponseEnvelope {\n let json: unknown;\n try {\n json = JSON.parse(line);\n } catch (cause) {\n throw new MalformedLeaseIpcFrameError(line, cause);\n }\n const result = LeaseIpcResponseEnvelopeSchema.safeParse(json);\n if (!result.success) {\n throw new MalformedLeaseIpcFrameError(line, result.error);\n }\n return result.data;\n },\n };\n}\n","/**\n * Worker-side heartbeat sender for the watchdog (T11628).\n *\n * A thin wrapper over {@link createLeaseIpcClient} that encodes a\n * `worker_heartbeat` frame and writes it to the supervisor over whatever\n * transport the caller owns. It is **degrade-by-default**: when no transport is\n * wired (no supervisor up / the daemon-off posture) {@link sendHeartbeat} is a\n * no-op, exactly mirroring how the LLM-queue admit path degrades when the\n * supervisor arbiter is absent. A managed worker calls {@link sendHeartbeat}\n * periodically (well inside the watchdog's NORMAL deadline) so the supervisor\n * knows it is alive; on a miss the watchdog SIGTERM→SIGKILLs it.\n *\n * The sender owns no socket: the caller supplies a `writeLine` sink (the\n * connected Unix-socket write half, a test double, …) — keeping this module free\n * of platform IO and trivially testable, consistent with the v1.0/v1.1 codecs.\n *\n * @packageDocumentation\n * @module @cleocode/runtime/daemon\n *\n * @epic T11625\n * @task T11628 — supervisor watchdog: worker heartbeat sender\n * @saga T11243 SG-RUNTIME-UNIFICATION\n * @see ./lease-ipc-client.ts — the codec this wraps\n */\n\nimport { createLeaseIpcClient, type LeaseIpcClient } from './lease-ipc-client.js';\n\n/**\n * A transport sink that writes one already-framed NDJSON line (including the\n * trailing `\\n`) to the supervisor. Returning a promise lets the caller await a\n * flush; the sender does not require it.\n */\nexport type WriteLine = (line: string) => void | Promise<void>;\n\n/**\n * A degrade-by-default worker heartbeat sender.\n *\n * Construct via {@link createHeartbeatSender}. When no transport is wired,\n * {@link HeartbeatSender.sendHeartbeat} is a no-op and {@link HeartbeatSender.isActive}\n * is `false`.\n */\nexport interface HeartbeatSender {\n /**\n * Send one `worker_heartbeat` for `childId`. A no-op (resolves immediately)\n * when no transport is wired — the worker degrades gracefully when no\n * supervisor is up.\n *\n * @param childId - The heartbeating child's logical id (registry key).\n * @param inFlightLlm - The worker's own view of whether it is currently\n * inside an LLM call. `true` lets the watchdog grant the\n * EXTENDED deadline so a slow-but-healthy long call is\n * never false-killed (AC2 · RISK-7).\n * @returns The correlation `id` of the sent frame, or `null` when no transport\n * is wired (the no-op path).\n */\n sendHeartbeat: (childId: string, inFlightLlm: boolean) => Promise<string | null>;\n\n /**\n * Whether a transport is wired. `false` means {@link sendHeartbeat} is a\n * no-op (no supervisor up).\n */\n isActive: () => boolean;\n}\n\n/**\n * Create a worker heartbeat sender.\n *\n * @param writeLine - The transport sink that writes one NDJSON line to the\n * supervisor, or `undefined`/`null` when no supervisor is up\n * (the no-op / degrade path).\n * @param client - An optional pre-built {@link LeaseIpcClient} codec (defaults\n * to a fresh one); injectable for tests.\n * @returns A {@link HeartbeatSender}.\n *\n * @example\n * ```ts\n * // With a connected supervisor socket:\n * const sender = createHeartbeatSender((line) => socket.write(line));\n * setInterval(() => sender.sendHeartbeat('worker-7', isInLlmCall()), 10_000);\n *\n * // No supervisor up — every sendHeartbeat is a silent no-op:\n * const offline = createHeartbeatSender(undefined);\n * await offline.sendHeartbeat('worker-7', false); // resolves to null\n * ```\n */\nexport function createHeartbeatSender(\n writeLine: WriteLine | undefined | null,\n client: LeaseIpcClient = createLeaseIpcClient(),\n): HeartbeatSender {\n const active = typeof writeLine === 'function';\n\n return {\n isActive(): boolean {\n return active;\n },\n\n async sendHeartbeat(childId: string, inFlightLlm: boolean): Promise<string | null> {\n // Degrade: no transport wired → no-op (mirrors the queue's degrade).\n if (!active || writeLine == null) {\n return null;\n }\n const { id, line } = client.encodeRequest({\n kind: 'worker_heartbeat',\n child_id: childId,\n in_flight_llm: inFlightLlm,\n });\n await writeLine(line);\n return id;\n },\n };\n}\n","/**\n * Daemon subsystem log-routing primitive.\n *\n * `createSubsystemLogger(name)` is the **single** logging surface the\n * `@cleocode/runtime/daemon` submodule exposes. It routes through the canonical\n * pino factory `getLogger` (`packages/core/src/logger.ts`) — NOT raw\n * `createWriteStream` and NOT `console.*` — so every daemon log line lands in\n * the rotating, retention-managed CLEO log files with a consistent structured\n * shape and a per-subsystem `subsystem` field.\n *\n * It supersedes the **5 ad-hoc logging conventions** the legacy daemons grew,\n * which R4-R7 will migrate onto this primitive:\n *\n * 1. `sentient/daemon.ts` — `createWriteStream(sentient.log)` /\n * `createWriteStream(sentient.err)` + `process.stderr.write` lines.\n * 2. `gc/daemon.ts` — `createWriteStream(gc.log)` / `createWriteStream(gc.err)`.\n * 3. `web.ts` — `console.*`.\n * 4. `docs-viewer.ts` — ad-hoc stdout/stderr writes.\n * 5. the `runtime` services — bespoke per-service logging.\n *\n * ## Migration guidance for R4-R7 adapters\n *\n * Each adapter (Studio, GC, web, docs-viewer) replaces its bespoke sink with:\n *\n * ```ts\n * import { createSubsystemLogger } from '@cleocode/runtime/daemon';\n * const log = createSubsystemLogger('studio');\n * log.info({ pid }, 'Studio started'); // not process.stderr.write(...)\n * log.error({ err }, 'Studio crashed'); // not createWriteStream('sentient.err')\n * ```\n *\n * Do not import pino directly and do not open file streams — the canonical\n * factory owns rotation, retention, level, and the `projectHash` correlation\n * field. Routing through it is what makes the daemon's logs observable via the\n * same `cleo` log surface as the rest of the system.\n *\n * @packageDocumentation\n * @module @cleocode/runtime/daemon\n *\n * @epic T11253 R2 — `@cleocode/runtime/daemon` submodule\n * @task T11368 — log routing primitive (pino getLogger canonical sink)\n * @saga T11243 SG-RUNTIME-UNIFICATION\n */\n\nimport { getLogger } from '@cleocode/core';\n\n/**\n * A structured log payload — a plain object of serializable fields merged into\n * the emitted log record (e.g. `{ pid, err }`).\n */\nexport type SubsystemLogFields = Record<string, unknown>;\n\n/**\n * The narrow logging surface a daemon subsystem uses.\n *\n * Each method accepts an optional structured `fields` object followed by the\n * message, mirroring pino's `(obj, msg)` call shape. This is intentionally\n * smaller than the full pino `Logger` so the daemon submodule does not leak the\n * pino type across its public boundary while still routing through the\n * canonical pino sink.\n */\nexport interface SubsystemLogger {\n /** Log at `debug` level. */\n debug: (fields: SubsystemLogFields, msg: string) => void;\n /** Log at `info` level. */\n info: (fields: SubsystemLogFields, msg: string) => void;\n /** Log at `warn` level. */\n warn: (fields: SubsystemLogFields, msg: string) => void;\n /** Log at `error` level. */\n error: (fields: SubsystemLogFields, msg: string) => void;\n}\n\n/**\n * Create the single per-subsystem logger for the daemon submodule.\n *\n * Routes through the canonical pino factory `getLogger('daemon')` and namespaces\n * the result with a per-subsystem child binding (`{ subsystem: name }`), so all\n * daemon log lines share the `daemon` subsystem root while remaining\n * individually attributable. Never opens a file stream and never writes to\n * `console`.\n *\n * @param name - The logical subsystem name (e.g. `'studio'`, `'gc'`, `'web'`).\n * @returns A {@link SubsystemLogger} bound to `name`.\n *\n * @example\n * ```ts\n * const log = createSubsystemLogger('studio');\n * log.info({ pid: 4242, port: 3456 }, 'Studio started');\n * ```\n */\nexport function createSubsystemLogger(name: string): SubsystemLogger {\n // Bind under the canonical 'daemon' root, then a per-subsystem child so each\n // subsystem's lines carry `{ subsystem: 'daemon', daemonSubsystem: name }`.\n const log = getLogger('daemon').child({ daemonSubsystem: name });\n return {\n debug: (fields, msg) => log.debug(fields, msg),\n info: (fields, msg) => log.info(fields, msg),\n warn: (fields, msg) => log.warn(fields, msg),\n error: (fields, msg) => log.error(fields, msg),\n };\n}\n","/**\n * `SubsystemRegistry` — drives the uniform daemon subsystem lifecycle.\n *\n * Aggregates {@link Subsystem}s declared via `defineSubsystem` and runs them\n * through a uniform `start → healthProbe → shutdown` lifecycle, firing the\n * typed {@link DaemonLifecycleHooks} in order. Health probes are aggregated\n * into a {@link HealthStatus} that projects onto the FROZEN `supervisor-ipc`\n * `MonitorResponse` the Rust supervisor consumes (AC4).\n *\n * Shutdown runs in reverse registration order (LIFO) so dependants stop before\n * their dependencies — the conventional supervised-process teardown ordering.\n *\n * @packageDocumentation\n * @module @cleocode/runtime/daemon\n *\n * @epic T11253 R2 — `@cleocode/runtime/daemon` submodule\n * @task T11367 — defineSubsystem + lifecycle/health registry + IPC client\n * @saga T11243 SG-RUNTIME-UNIFICATION\n */\n\nimport {\n type DaemonLifecycleHooks,\n type HealthStatus,\n type Subsystem,\n type SubsystemHealth,\n type SubsystemLifecyclePhase,\n summarizeHealth,\n} from '@cleocode/contracts';\n\n/**\n * A registered subsystem plus its runtime bookkeeping.\n *\n * The `context` is the value `start()` resolved to, threaded back into\n * `shutdown()`. It is type-erased to `unknown` inside the registry because the\n * registry holds subsystems of heterogeneous context types; each entry's\n * `subsystem.shutdown` is the only consumer and re-narrows it at the call site\n * via the subsystem's own typing — no `any`, and the erased value never escapes\n * the registry surface.\n */\ninterface RegisteredSubsystem {\n readonly subsystem: Subsystem<unknown>;\n started: boolean;\n context: unknown;\n}\n\n/** Normalize an unknown thrown value into an `Error`. */\nfunction toError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value));\n}\n\n/**\n * The registry that owns and drives the daemon's subsystems.\n *\n * Construct with optional {@link DaemonLifecycleHooks}; register subsystems,\n * then `startAll()` / `aggregateHealth()` / `shutdownAll()`.\n */\nexport class SubsystemRegistry {\n readonly #entries: RegisteredSubsystem[] = [];\n readonly #hooks: DaemonLifecycleHooks;\n\n /**\n * @param hooks - Optional lifecycle hooks fired around start/shutdown and on\n * any lifecycle error.\n */\n constructor(hooks: DaemonLifecycleHooks = {}) {\n this.#hooks = hooks;\n }\n\n /**\n * Register a subsystem. Registration order defines start order; shutdown runs\n * in reverse.\n *\n * @typeParam TContext - The subsystem's start→shutdown context type.\n * @param subsystem - A subsystem produced by `defineSubsystem`.\n * @throws {Error} When a subsystem with the same `name` is already registered.\n */\n register<TContext>(subsystem: Subsystem<TContext>): void {\n if (this.#entries.some((entry) => entry.subsystem.name === subsystem.name)) {\n throw new Error(`SubsystemRegistry: duplicate subsystem name '${subsystem.name}'`);\n }\n // Widen the context type to `unknown` for heterogeneous storage. The\n // subsystem's own `start`/`shutdown` remain individually well-typed; only\n // the registry's internal handle is erased, and it never leaks out.\n this.#entries.push({\n subsystem: subsystem as Subsystem<unknown>,\n started: false,\n context: undefined,\n });\n }\n\n /** The names of all registered subsystems, in registration order. */\n get names(): readonly string[] {\n return this.#entries.map((entry) => entry.subsystem.name);\n }\n\n /**\n * Start every registered subsystem in registration order.\n *\n * For each subsystem: awaits `start()`, stores the resolved context, then\n * fires `onStart`. If `start()` throws, fires `onError` with phase `start`\n * and re-throws — a failed start is surfaced, never swallowed.\n *\n * @throws The first subsystem `start()` error (after firing `onError`).\n */\n async startAll(): Promise<void> {\n for (const entry of this.#entries) {\n try {\n entry.context = await entry.subsystem.start();\n entry.started = true;\n await this.#hooks.onStart?.(entry.subsystem.name);\n } catch (cause) {\n await this.#fireError(entry.subsystem.name, cause, 'start');\n throw toError(cause);\n }\n }\n }\n\n /**\n * Probe every registered subsystem and aggregate into a {@link HealthStatus}.\n *\n * A subsystem whose `healthProbe()` rejects does not abort the aggregate:\n * `onError` fires with phase `healthProbe` and the subsystem is reported as a\n * `stopped` row carrying the error in its `detail`, so the supervisor still\n * receives a complete snapshot.\n *\n * @returns The aggregate health snapshot (projects onto `MonitorResponse`).\n */\n async aggregateHealth(): Promise<HealthStatus> {\n const rows: SubsystemHealth[] = [];\n for (const entry of this.#entries) {\n try {\n rows.push(await entry.subsystem.healthProbe());\n } catch (cause) {\n await this.#fireError(entry.subsystem.name, cause, 'healthProbe');\n rows.push({\n child_id: entry.subsystem.name,\n pid: 0,\n state: 'stopped',\n restart_count: 0,\n detail: `healthProbe failed: ${toError(cause).message}`,\n });\n }\n }\n return summarizeHealth(rows);\n }\n\n /**\n * Shut down every started subsystem in reverse registration order (LIFO).\n *\n * Each subsystem's `shutdown(context)` is awaited and `onShutdown` fires. A\n * throwing `shutdown` fires `onError` with phase `shutdown` but does NOT\n * abort the teardown of the remaining subsystems — best-effort graceful\n * shutdown of the whole daemon.\n */\n async shutdownAll(): Promise<void> {\n for (let i = this.#entries.length - 1; i >= 0; i -= 1) {\n const entry = this.#entries[i];\n if (entry === undefined || !entry.started) continue;\n try {\n await entry.subsystem.shutdown(entry.context);\n entry.started = false;\n await this.#hooks.onShutdown?.(entry.subsystem.name);\n } catch (cause) {\n await this.#fireError(entry.subsystem.name, cause, 'shutdown');\n // Continue tearing down the rest — do not abort on one failure.\n }\n }\n }\n\n /** Fire the `onError` hook, swallowing any error the hook itself throws. */\n async #fireError(name: string, cause: unknown, phase: SubsystemLifecyclePhase): Promise<void> {\n try {\n await this.#hooks.onError?.(name, toError(cause), phase);\n } catch {\n // A throwing error-hook must not mask the original failure.\n }\n }\n}\n","/**\n * Typed NDJSON client for the FROZEN `supervisor-ipc` v1.0 contract.\n *\n * The Rust `cleo-supervisor` is the server; a CLEO daemon process is a client\n * that sends {@link SupervisorIpcRequestEnvelope}s and receives\n * {@link SupervisorIpcResponseEnvelope}s, one newline-delimited JSON\n * (NDJSON) envelope per line. This module is a transport-agnostic codec: it\n * encodes outbound envelopes to NDJSON lines and decodes inbound lines through\n * the frozen Zod schemas. **Every inbound line is parsed** — a malformed or\n * schema-violating frame is rejected with a typed {@link MalformedIpcFrameError}\n * and never silently dropped (T11367 AC2).\n *\n * The codec does not own a socket — callers feed it raw lines from whatever\n * transport (Unix socket, named pipe, test pair) they own, keeping the daemon\n * submodule free of platform IO and trivially testable.\n *\n * @packageDocumentation\n * @module @cleocode/runtime/daemon\n *\n * @epic T11253 R2 — `@cleocode/runtime/daemon` submodule\n * @task T11367 — defineSubsystem + lifecycle/health registry + IPC client\n * @saga T11243 SG-RUNTIME-UNIFICATION\n */\n\nimport {\n SUPERVISOR_IPC_PROTOCOL_VERSION,\n type SupervisorIpcRequest,\n type SupervisorIpcRequestEnvelope,\n SupervisorIpcRequestEnvelopeSchema,\n type SupervisorIpcResponseEnvelope,\n SupervisorIpcResponseEnvelopeSchema,\n} from '@cleocode/contracts';\n\n/**\n * A typed error raised when an inbound NDJSON line cannot be decoded into a\n * valid {@link SupervisorIpcResponseEnvelope}.\n *\n * Carries the offending raw `line` and the underlying `cause` (a JSON\n * `SyntaxError` or a Zod validation error) so callers can log + correlate.\n * Invalid frames are surfaced as this error — never silently dropped.\n */\nexport class MalformedIpcFrameError extends Error {\n /** The raw NDJSON line that failed to decode. */\n readonly line: string;\n\n /**\n * @param line - The offending raw NDJSON line.\n * @param cause - The underlying JSON or Zod error.\n */\n constructor(line: string, cause: unknown) {\n super(`Malformed supervisor-ipc frame: ${cause instanceof Error ? cause.message : cause}`);\n this.name = 'MalformedIpcFrameError';\n this.line = line;\n this.cause = cause;\n }\n}\n\n/**\n * A transport-agnostic codec for the FROZEN `supervisor-ipc` v1.0 wire format.\n *\n * Construct via {@link createSupervisorIpcClient}. The client is stateful only\n * in its monotonic correlation-id counter; it owns no socket.\n */\nexport interface SupervisorIpcClient {\n /**\n * Encode a request body into a complete, newline-terminated NDJSON envelope\n * line ready to write to the transport.\n *\n * Stamps the frozen protocol version and a fresh correlation `id`, validates\n * the result against {@link SupervisorIpcRequestEnvelopeSchema}, and returns\n * the serialized line (including the trailing `\\n`).\n *\n * @param request - The request body (validated by the envelope schema).\n * @returns `{ id, line }` — the correlation id and the NDJSON line to send.\n */\n encodeRequest: (request: SupervisorIpcRequest) => { id: string; line: string };\n\n /**\n * Decode a single inbound NDJSON line into a typed response envelope.\n *\n * Parses the line as JSON, then validates it against\n * {@link SupervisorIpcResponseEnvelopeSchema}. A malformed or\n * schema-violating line throws {@link MalformedIpcFrameError} — it is never\n * silently dropped.\n *\n * @param line - A single NDJSON line (without the trailing newline).\n * @returns The decoded {@link SupervisorIpcResponseEnvelope}.\n * @throws {MalformedIpcFrameError} When the line is not a valid response frame.\n */\n decodeResponseLine: (line: string) => SupervisorIpcResponseEnvelope;\n}\n\n/**\n * Generate a process-unique correlation id for an IPC request envelope.\n *\n * @param seq - The client's monotonic sequence number.\n * @returns A correlation id of the form `c<seq>-<random>`.\n */\nfunction nextCorrelationId(seq: number): string {\n return `c${seq}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\n/**\n * Create a typed NDJSON codec for the FROZEN `supervisor-ipc` v1.0 contract.\n *\n * @returns A {@link SupervisorIpcClient}.\n *\n * @example\n * ```ts\n * const client = createSupervisorIpcClient();\n * const { id, line } = client.encodeRequest({ kind: 'health' });\n * socket.write(line);\n * socket.on('line', (raw) => {\n * const env = client.decodeResponseLine(raw); // throws MalformedIpcFrameError on bad frame\n * if (env.response.kind === 'health') { /* ... *\\/ }\n * });\n * ```\n */\nexport function createSupervisorIpcClient(): SupervisorIpcClient {\n let seq = 0;\n\n return {\n encodeRequest(request: SupervisorIpcRequest): { id: string; line: string } {\n seq += 1;\n const id = nextCorrelationId(seq);\n const envelope: SupervisorIpcRequestEnvelope = {\n protocol_version: SUPERVISOR_IPC_PROTOCOL_VERSION,\n id,\n direction: 'request',\n request,\n };\n // Validate before serialization so we never emit an off-contract frame.\n const parsed = SupervisorIpcRequestEnvelopeSchema.parse(envelope);\n return { id, line: `${JSON.stringify(parsed)}\\n` };\n },\n\n decodeResponseLine(line: string): SupervisorIpcResponseEnvelope {\n let json: unknown;\n try {\n json = JSON.parse(line);\n } catch (cause) {\n throw new MalformedIpcFrameError(line, cause);\n }\n const result = SupervisorIpcResponseEnvelopeSchema.safeParse(json);\n if (!result.success) {\n throw new MalformedIpcFrameError(line, result.error);\n }\n return result.data;\n },\n };\n}\n"],"mappings":"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","names":["nextCorrelationId"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/gateway/rpc/codec.ts","../src/gateway/rpc/server.ts"],"sourcesContent":["/**\n * NDJSON codec for the CLI-RPC transport.\n *\n * Implements the SAME framing discipline as the FROZEN `supervisor-ipc` v1.0\n * contract — one zod-validated JSON envelope per newline-delimited line, with\n * an explicit `protocol_version` negotiation step — but for the gateway-RPC\n * frame set (`@cleocode/contracts/gateway/rpc`). The supervisor-ipc module is\n * frozen and is NOT edited; this codec re-applies its pattern.\n *\n * The codec is intentionally socket-agnostic: it owns line buffering, JSON\n * parse, zod decode, and version negotiation, returning typed results the\n * server maps onto `node:net` reads/writes. This keeps the parse/validate logic\n * unit-testable without binding a real socket.\n *\n * @task T11449\n * @epic T11254\n * @saga T11243\n */\n\nimport {\n GATEWAY_RPC_PROTOCOL_VERSION,\n type GatewayRpcErrorCode,\n type GatewayRpcErrorFrame,\n type GatewayRpcFrame,\n GatewayRpcFrameSchema,\n type GatewayRpcRequestFrame,\n type GatewayRpcResponseFrame,\n isFrozenGatewayRpcVersion,\n} from '@cleocode/contracts/gateway/rpc';\n\n/** Newline delimiter for the NDJSON wire (mirrors supervisor-ipc). */\nconst NEWLINE = '\\n';\n\n/**\n * Encode any RPC frame to a single NDJSON line (JSON + trailing `\\n`).\n *\n * @param frame - The response or error frame to serialize.\n * @returns A newline-terminated JSON string ready to write to the socket.\n */\nexport function encodeFrame(frame: GatewayRpcResponseFrame | GatewayRpcErrorFrame): string {\n return `${JSON.stringify(frame)}${NEWLINE}`;\n}\n\n/**\n * Build a protocol-level error frame.\n *\n * @param id - Correlation id (`'0'` when the originating frame's id is unknown).\n * @param code - One of the frozen {@link GatewayRpcErrorCode} values.\n * @param message - Human-readable detail.\n * @returns A fully-formed {@link GatewayRpcErrorFrame}.\n */\nexport function buildErrorFrame(\n id: string,\n code: GatewayRpcErrorCode,\n message: string,\n): GatewayRpcErrorFrame {\n return {\n protocol_version: GATEWAY_RPC_PROTOCOL_VERSION,\n id,\n direction: 'error',\n error: { code, message },\n };\n}\n\n/**\n * The discriminated outcome of decoding a single inbound NDJSON line.\n *\n * - `request` — a valid, version-matched request frame ready to route.\n * - `error` — a protocol-level error frame the server should write straight\n * back (parse failure, version mismatch, or malformed frame). The\n * server never routes these to the gateway.\n */\nexport type DecodeResult =\n | { kind: 'request'; frame: GatewayRpcRequestFrame }\n | { kind: 'error'; frame: GatewayRpcErrorFrame };\n\n/**\n * Decode + validate a single inbound NDJSON line into a routable request or a\n * protocol-level error frame.\n *\n * Steps (the supervisor-ipc pattern, applied to gateway frames):\n * 1. JSON parse — failure → `E_RPC_PARSE`.\n * 2. zod decode against {@link GatewayRpcFrameSchema} — failure → `E_RPC_BAD_FRAME`.\n * 3. version negotiation via {@link isFrozenGatewayRpcVersion} —\n * mismatch → `E_RPC_BAD_VERSION`.\n * 4. direction must be `request` (a server never receives response/error\n * frames) — otherwise → `E_RPC_BAD_FRAME`.\n *\n * The best-effort correlation `id` is recovered from the raw JSON when possible\n * so the client can match the error to its request even on a decode failure.\n *\n * @param line - One trimmed NDJSON line (no trailing newline).\n * @returns A {@link DecodeResult}.\n */\nexport function decodeLine(line: string): DecodeResult {\n let raw: unknown;\n try {\n raw = JSON.parse(line);\n } catch {\n return { kind: 'error', frame: buildErrorFrame('0', 'E_RPC_PARSE', 'invalid JSON line') };\n }\n\n // Best-effort id recovery for error correlation before full validation.\n const recoveredId = recoverId(raw);\n\n const parsed = GatewayRpcFrameSchema.safeParse(raw);\n if (!parsed.success) {\n return {\n kind: 'error',\n frame: buildErrorFrame(recoveredId, 'E_RPC_BAD_FRAME', 'frame failed schema validation'),\n };\n }\n\n const frame: GatewayRpcFrame = parsed.data;\n\n if (!isFrozenGatewayRpcVersion(frame.protocol_version)) {\n return {\n kind: 'error',\n frame: buildErrorFrame(\n frame.id,\n 'E_RPC_BAD_VERSION',\n `unsupported protocol version ${frame.protocol_version}; server speaks ${GATEWAY_RPC_PROTOCOL_VERSION}`,\n ),\n };\n }\n\n if (frame.direction !== 'request') {\n return {\n kind: 'error',\n frame: buildErrorFrame(\n frame.id,\n 'E_RPC_BAD_FRAME',\n `server cannot accept a ${frame.direction} frame`,\n ),\n };\n }\n\n return { kind: 'request', frame };\n}\n\n/**\n * Recover a best-effort correlation id from raw decoded JSON, used so a\n * validation failure can still be correlated to the client's request.\n *\n * @param raw - The result of `JSON.parse` on the inbound line.\n * @returns The string `id` if present and non-empty, else `'0'`.\n */\nfunction recoverId(raw: unknown): string {\n if (raw && typeof raw === 'object' && 'id' in raw) {\n const id = (raw as { id: unknown }).id;\n if (typeof id === 'string' && id.length > 0) return id;\n }\n return '0';\n}\n\n/**\n * A streaming line splitter for the NDJSON wire.\n *\n * `node:net` sockets deliver arbitrary chunk boundaries; a single `data` event\n * may carry a partial line or several lines. This buffer accumulates bytes and\n * yields only complete lines (split on `\\n`), matching the supervisor-ipc\n * client's `readline`-style framing without depending on `readline` (so it can\n * be driven directly off socket chunks).\n */\nexport class LineBuffer {\n /** Pending bytes that have not yet completed a line. */\n private buffer = '';\n\n /**\n * Push a decoded UTF-8 chunk; return every complete line it completes.\n *\n * @param chunk - A UTF-8 string chunk from the socket.\n * @returns Zero or more complete lines (trailing newline stripped, blanks\n * skipped).\n */\n push(chunk: string): string[] {\n this.buffer += chunk;\n const lines: string[] = [];\n let idx = this.buffer.indexOf(NEWLINE);\n while (idx !== -1) {\n const line = this.buffer.slice(0, idx).trim();\n this.buffer = this.buffer.slice(idx + 1);\n if (line.length > 0) lines.push(line);\n idx = this.buffer.indexOf(NEWLINE);\n }\n return lines;\n }\n}\n","/**\n * CLI-RPC unix-socket transport adapter — routes NDJSON frames through the gateway.\n *\n * Serves the gateway over a long-lived unix-domain socket using newline-delimited\n * JSON (NDJSON): one zod-validated {@link GatewayRpcFrame} per line. Any local\n * client (the `cleo` CLI talking to a warm daemon, Studio's server process, a\n * test harness) can open the socket, write `request` frames, and read back\n * `response`/`error` frames — without paying the per-invocation cold-start of\n * spawning a fresh `cleo` process.\n *\n * This is the THIRD transport adapter, mirroring the just-merged MCP stdio\n * adapter (`packages/runtime/src/gateway/mcp/`):\n *\n * RPC request frame { direction:'request', request:{…} }\n * → decode + validate (`@cleocode/contracts/gateway/rpc`)\n * → DispatchRequest { source: 'rpc', … } (re-validated at the boundary)\n * → injected {@link GatewayHandler}.handle()\n * → DispatchResponse (LAFS envelope) → RPC response frame\n *\n * The adapter owns ONLY wire concerns: socket lifecycle, line framing, version\n * negotiation, error rendering, and per-connection cleanup. All domain logic\n * (resolution, validation, middleware, handler execution) lives behind the\n * {@link GatewayHandler} — exactly mirroring the CLI and MCP adapters. There is\n * NO `process.exit` and NO error-render inside the handlers (R3 contract); the\n * adapter never calls `process.exit` at all (lifecycle is the caller's).\n *\n * The request `source` is forced to `'rpc'` regardless of what the client sent,\n * so a client cannot impersonate another transport.\n *\n * @task T11449\n * @epic T11254\n * @saga T11243\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { existsSync, unlinkSync } from 'node:fs';\nimport { createServer, type Socket } from 'node:net';\nimport type { DispatchRequest } from '@cleocode/contracts/gateway';\nimport type {\n GatewayRpcRequestFrame,\n GatewayRpcResponseFrame,\n} from '@cleocode/contracts/gateway/rpc';\nimport { GATEWAY_RPC_PROTOCOL_VERSION } from '@cleocode/contracts/gateway/rpc';\nimport { getLogger } from '@cleocode/core';\nimport {\n bindConnectionSession,\n runWithConnectionHandle,\n unbindConnectionSession,\n} from '@cleocode/core/internal';\nimport type { GatewayHandler } from '../index.js';\nimport { buildErrorFrame, decodeLine, encodeFrame, LineBuffer } from './codec.js';\nimport type { RpcServerHandle, RpcServerOptions } from './types.js';\n\n/**\n * Route a single decoded request frame through the gateway handler and build\n * the correlated response frame.\n *\n * The request's `source` is forced to `'rpc'`; the frame `id` is reused as the\n * response correlation id (and, when the client omitted a `requestId` shaped\n * for tracing, the frame id flows through as the dispatch `requestId`). Thrown\n * handler errors are caught and rendered as a protocol-level `E_RPC_INTERNAL`\n * error frame — never propagated as an unhandled rejection or `process.exit`.\n *\n * ## Connection-scoped session binding (T11640 · Epic T11638)\n *\n * The daemon serves many connections over one process, so it cannot rely on the\n * single-process session-context singleton for identity. Instead, a frame on a\n * connection that declares a `sessionId` binds it into the `{connId → sessionId}`\n * registry, and the whole dispatch runs inside {@link runWithConnectionHandle}\n * so `core`'s `resolveCurrentSession` resolves THIS connection's session (its\n * highest-precedence tier) rather than \"whoever wrote the DB last\". When a frame\n * carries no `sessionId`, no binding is performed and resolution transparently\n * falls through to the env / active tiers — preserving existing behavior for\n * anonymous frames.\n *\n * ### Intra-connection bleed safety\n *\n * Frames on one connection are dispatched CONCURRENTLY (each `routeFrame` is\n * fired without awaiting the prior one), and registry binding is last-write-wins.\n * To stop a still-in-flight Frame A from resolving a sibling Frame B's session,\n * the frame's OWN declared `sessionId` is snapshotted into the per-frame\n * connection handle ({@link runWithConnectionHandle}'s third arg) — that snapshot\n * is authoritative for the dispatch, so a later re-bind of the same `connId`\n * cannot retroactively change who Frame A is. The registry binding remains only\n * for the late-binding path used by frames that declare no session of their own.\n *\n * @param handler - The injected transport-neutral gateway handler.\n * @param frame - A validated, version-matched request frame.\n * @param connId - The opaque per-connection id (bound at accept-time). Omitted\n * only by direct/test callers that route a single frame outside a socket; a\n * fresh ephemeral id is minted so the call still runs in an isolated\n * connection-handle scope.\n * @returns The response or error frame to write back on the same connection.\n */\nexport async function routeFrame(\n handler: GatewayHandler,\n frame: GatewayRpcRequestFrame,\n connId: string = randomUUID(),\n): Promise<GatewayRpcResponseFrame | ReturnType<typeof buildErrorFrame>> {\n const request: DispatchRequest = {\n ...frame.request,\n // Force the transport of origin — a client cannot impersonate cli/mcp/http.\n source: 'rpc',\n requestId: frame.request.requestId,\n };\n\n // Bind this connection to the session the frame declared (empty/absent ids are\n // ignored by the registry). This feeds the late-binding path for any sibling\n // frame that declares no session of its own. The per-frame snapshot below — not\n // this mutable registry — is what THIS dispatch resolves against, so a\n // concurrent sibling frame's re-bind cannot bleed into it.\n if (request.sessionId) {\n bindConnectionSession(connId, request.sessionId);\n }\n\n try {\n // Snapshot the frame's own session into the per-frame handle so this dispatch\n // is immune to concurrent last-write-wins re-binds of the same connId.\n const response = await runWithConnectionHandle(\n connId,\n () => handler.handle(request),\n request.sessionId,\n );\n return {\n protocol_version: GATEWAY_RPC_PROTOCOL_VERSION,\n id: frame.id,\n direction: 'response',\n response,\n };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return buildErrorFrame(frame.id, 'E_RPC_INTERNAL', message);\n }\n}\n\n/**\n * Handle a single client connection: buffer NDJSON lines, decode each, route\n * request frames through the gateway, and write back response/error frames.\n *\n * Each connection owns its own {@link LineBuffer}, so partial-line chunk\n * boundaries are reassembled per socket. Frames are processed concurrently;\n * each correlated response carries the originating frame `id`, so the client\n * can demultiplex out-of-order completions.\n *\n * ## Accept-time session binding (T11640)\n *\n * A fresh opaque `connId` is minted per accepted socket and threaded into every\n * {@link routeFrame} call so the connection's declared session is bound into the\n * `{connId → sessionId}` registry and made the authoritative identity for that\n * dispatch (see {@link routeFrame}). On socket `close` the binding is removed so\n * the registry never grows unbounded across a long-lived daemon process.\n *\n * @param handler - The injected gateway handler.\n * @param socket - The accepted `node:net` connection.\n * @param log - The adapter's pino logger.\n */\nfunction handleConnection(\n handler: GatewayHandler,\n socket: Socket,\n log: ReturnType<typeof getLogger>,\n): void {\n socket.setEncoding('utf8');\n const lines = new LineBuffer();\n const connId = randomUUID();\n\n socket.on('data', (chunk: string) => {\n for (const line of lines.push(chunk)) {\n const decoded = decodeLine(line);\n if (decoded.kind === 'error') {\n socket.write(encodeFrame(decoded.frame));\n continue;\n }\n routeFrame(handler, decoded.frame, connId)\n .then((out) => {\n if (!socket.destroyed) socket.write(encodeFrame(out));\n })\n .catch((err: unknown) => {\n // routeFrame already traps handler errors; this guards the framing path.\n const message = err instanceof Error ? err.message : String(err);\n log.warn({ err }, 'rpc frame routing failed');\n if (!socket.destroyed) {\n socket.write(encodeFrame(buildErrorFrame(decoded.frame.id, 'E_RPC_INTERNAL', message)));\n }\n });\n }\n });\n\n socket.on('error', (err) => {\n log.warn({ err }, 'rpc connection error');\n });\n\n // Release the connection→session binding when the socket goes away so the\n // in-memory registry stays bounded across the daemon's lifetime (T11640).\n socket.on('close', () => {\n unbindConnectionSession(connId);\n });\n}\n\n/**\n * Start the CLI-RPC unix-socket server over the gateway.\n *\n * Binds {@link RpcServerOptions.socketPath}, accepts connections, and routes\n * each inbound NDJSON `request` frame through the injected {@link GatewayHandler}.\n * The caller assembles the handler (via `createGatewayHandler` with its domain\n * handlers + middleware) and injects it here — this adapter never builds\n * handlers itself, keeping the runtime free of any `@cleocode/cleo` dependency.\n *\n * Returns once the socket is listening. The returned {@link RpcServerHandle}\n * exposes an idempotent `close()` for deterministic teardown (daemon shutdown,\n * tests). The adapter never calls `process.exit`.\n *\n * @param handler - The transport-neutral gateway handler to route through.\n * @param opts - Socket path + lifecycle options.\n * @returns A promise resolving to the live {@link RpcServerHandle}.\n */\nexport function startRpcServer(\n handler: GatewayHandler,\n opts: RpcServerOptions,\n): Promise<RpcServerHandle> {\n const log = getLogger('gateway-rpc');\n const removeStale = opts.removeStaleSocket ?? true;\n\n if (removeStale && existsSync(opts.socketPath)) {\n try {\n unlinkSync(opts.socketPath);\n } catch (err) {\n log.warn({ err, socketPath: opts.socketPath }, 'failed to unlink stale rpc socket');\n }\n }\n\n const server = createServer((socket) => handleConnection(handler, socket, log));\n\n return new Promise<RpcServerHandle>((resolve, reject) => {\n server.once('error', reject);\n server.listen(opts.socketPath, () => {\n server.removeListener('error', reject);\n log.info(\n { socketPath: opts.socketPath, version: GATEWAY_RPC_PROTOCOL_VERSION },\n 'rpc server listening',\n );\n\n let closed = false;\n const handle: RpcServerHandle = {\n server,\n socketPath: opts.socketPath,\n close(): Promise<void> {\n if (closed) return Promise.resolve();\n closed = true;\n return new Promise<void>((res) => {\n server.close(() => {\n if (existsSync(opts.socketPath)) {\n try {\n unlinkSync(opts.socketPath);\n } catch {\n // best-effort cleanup; the OS reclaims the node on exit anyway.\n }\n }\n res();\n });\n });\n },\n };\n resolve(handle);\n });\n });\n}\n"],"mappings":"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","names":["GATEWAY_RPC_PROTOCOL_VERSION","GATEWAY_RPC_PROTOCOL_VERSION"]}