@johpaz/hive-sdk 0.1.3 → 0.1.5

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 (272) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/README.md +78 -23
  3. package/bunfig.toml +4 -2
  4. package/docs/API-AGENTS.md +78 -27
  5. package/docs/API-CONTEXT-COMPILER.md +31 -34
  6. package/docs/API-TOOLS-SKILLS-CHANNELS.md +58 -22
  7. package/docs/HIVE-HARNESS.md +1 -1
  8. package/docs/INDEX.md +4 -4
  9. package/docs/TEMPLATE-HIVE-APP.md +10 -10
  10. package/package.json +9 -4
  11. package/packages/cli/package.json +2 -2
  12. package/packages/cli/src/commands/create-app.test.ts +36 -7
  13. package/packages/cli/src/commands/init.ts +3 -3
  14. package/packages/cli/src/commands/run.ts +1 -1
  15. package/packages/cli/src/commands/test.ts +37 -25
  16. package/packages/cli/src/commands/trace.ts +30 -28
  17. package/packages/cli/templates/hive-app/.env.example +10 -2
  18. package/packages/cli/templates/hive-app/README.md +103 -0
  19. package/packages/cli/templates/hive-app/hive.config.ts +9 -3
  20. package/packages/cli/templates/hive-app/src/agents/coordinator.ts +8 -1
  21. package/packages/cli/templates/hive-app/src/main.ts +12 -19
  22. package/packages/core/package.json +5 -4
  23. package/packages/core/src/agent/acceptance-checks.ts +166 -0
  24. package/packages/core/src/agent/agent-catalog.ts +348 -0
  25. package/packages/core/src/agent/agent-loop.ts +1373 -0
  26. package/packages/core/src/agent/capability-search.ts +186 -0
  27. package/packages/core/src/agent/catalog-selector.ts +103 -0
  28. package/packages/core/src/agent/{Compaction.ts → compaction.ts} +86 -63
  29. package/packages/core/src/agent/context-compiler.ts +689 -0
  30. package/packages/core/src/agent/conversation-store.ts +381 -0
  31. package/packages/core/src/agent/curator.ts +276 -0
  32. package/packages/core/src/agent/delegation-runtime.ts +241 -0
  33. package/packages/core/src/agent/goal-runner.ts +323 -0
  34. package/packages/core/src/agent/index.ts +17 -12
  35. package/packages/core/src/agent/llm-client.ts +266 -0
  36. package/packages/core/src/agent/llm-providers/anthropic.ts +264 -0
  37. package/packages/core/src/agent/llm-providers/deepseek.ts +8 -0
  38. package/packages/core/src/agent/{providers → llm-providers}/gemini.ts +98 -60
  39. package/packages/core/src/agent/llm-providers/groq.ts +5 -0
  40. package/packages/core/src/agent/llm-providers/hiveagents.ts +253 -0
  41. package/packages/core/src/agent/{providers → llm-providers}/interface.ts +73 -13
  42. package/packages/core/src/agent/llm-providers/kimi.ts +8 -0
  43. package/packages/core/src/agent/llm-providers/minimax.ts +13 -0
  44. package/packages/core/src/agent/llm-providers/mistral.ts +5 -0
  45. package/packages/core/src/agent/llm-providers/modelscope.ts +5 -0
  46. package/packages/core/src/agent/llm-providers/nvidia.ts +5 -0
  47. package/packages/core/src/agent/{providers → llm-providers}/ollama.ts +31 -5
  48. package/packages/core/src/agent/llm-providers/openai-compat-base.ts +418 -0
  49. package/packages/core/src/agent/llm-providers/openai.ts +5 -0
  50. package/packages/core/src/agent/llm-providers/opencode-go.ts +9 -0
  51. package/packages/core/src/agent/llm-providers/openrouter.ts +5 -0
  52. package/packages/core/src/agent/llm-providers/qwen.ts +5 -0
  53. package/packages/core/src/agent/llm-providers/z-ai.ts +5 -0
  54. package/packages/core/src/agent/minimal-loadout.ts +47 -0
  55. package/packages/core/src/agent/playbook-selector.ts +119 -0
  56. package/packages/core/src/agent/{PromptBuilder.ts → prompt-builder.ts} +21 -22
  57. package/packages/core/src/{harness → agent}/proof-packet.ts +16 -21
  58. package/packages/core/src/agent/providers/index.ts +35 -16
  59. package/packages/core/src/agent/reflector.ts +320 -0
  60. package/packages/core/src/agent/routing-intent.ts +22 -0
  61. package/packages/core/src/{harness → agent}/run-epoch.ts +4 -3
  62. package/packages/core/src/{harness → agent}/run-store.ts +142 -81
  63. package/packages/core/src/agent/{Service.ts → service.ts} +37 -26
  64. package/packages/core/src/agent/skill-selector.ts +374 -0
  65. package/packages/core/src/agent/stuck-loop.ts +209 -0
  66. package/packages/core/src/agent/{selectors/ToolSelector.ts → tool-selector.ts} +188 -178
  67. package/packages/core/src/{ace/Tracer.ts → agent/tracer.ts} +37 -27
  68. package/packages/core/src/api/createAgent.test.ts +139 -27
  69. package/packages/core/src/api/createAgent.ts +232 -44
  70. package/packages/core/src/artifacts/store.ts +162 -0
  71. package/packages/core/src/canvas/canvas-manager.ts +161 -0
  72. package/packages/core/src/canvas/canvas.test.ts +8 -4
  73. package/packages/core/src/canvas/emitter.ts +131 -80
  74. package/packages/core/src/canvas/index.ts +1 -3
  75. package/packages/core/src/channels/base.ts +9 -1
  76. package/packages/core/src/channels/discord.ts +5 -4
  77. package/packages/core/src/channels/manager.ts +122 -30
  78. package/packages/core/src/channels/slack.ts +5 -4
  79. package/packages/core/src/channels/telegram.ts +36 -6
  80. package/packages/core/src/channels/webchat.ts +11 -10
  81. package/packages/core/src/channels/whatsapp.ts +23 -7
  82. package/packages/core/src/config/index.ts +13 -2
  83. package/packages/core/src/config/loader.ts +71 -29
  84. package/packages/core/src/ethics/EthicsGuard.test.ts +90 -36
  85. package/packages/core/src/ethics/EthicsGuard.ts +51 -47
  86. package/packages/core/src/events/agent-bus.ts +44 -68
  87. package/packages/core/src/events/channel-narration.ts +150 -0
  88. package/packages/core/src/events/narration.ts +82 -0
  89. package/packages/core/src/events/tool-narration.ts +62 -0
  90. package/packages/core/src/gateway/delegation-groups.ts +258 -0
  91. package/packages/core/src/{harness → gateway}/durable-queue.ts +102 -42
  92. package/packages/core/src/{harness → gateway}/job-store.ts +85 -48
  93. package/packages/core/src/gateway/lane-queue.ts +173 -0
  94. package/packages/core/src/gateway/notification-inbox.ts +57 -0
  95. package/packages/core/src/gateway/server.ts +1 -1
  96. package/packages/core/src/harness/index.ts +46 -27
  97. package/packages/core/src/index.ts +33 -20
  98. package/packages/core/src/mcp/hot-reload.ts +32 -23
  99. package/packages/core/src/mcp/index.ts +6 -3
  100. package/packages/core/src/mcp/singleton.ts +1 -4
  101. package/packages/core/src/mcp/tool-sync.ts +138 -0
  102. package/packages/core/src/memory/Scratchpad.test.ts +39 -20
  103. package/packages/core/src/memory/Scratchpad.ts +27 -34
  104. package/packages/core/src/multimodal/vision-service.ts +44 -38
  105. package/packages/core/src/resilience/retry.ts +95 -0
  106. package/packages/core/src/scheduler/CronScheduler.ts +334 -287
  107. package/packages/core/src/scheduler/index.ts +9 -7
  108. package/packages/core/src/scheduler/integration.ts +46 -26
  109. package/packages/core/src/scheduler/scheduler.test.ts +9 -13
  110. package/packages/core/src/scheduler/types.ts +7 -2
  111. package/packages/core/src/security/Pairing.ts +1 -1
  112. package/packages/core/src/skills/bundled/a2ui/a2ui_dashboard/SKILL.md +176 -0
  113. package/packages/core/src/skills/bundled/a2ui/a2ui_form/SKILL.md +202 -0
  114. package/packages/core/src/skills/bundled/a2ui/a2ui_interactive/SKILL.md +206 -0
  115. package/packages/core/src/skills/bundled/agents/agent_spawner/SKILL.md +173 -0
  116. package/packages/core/src/skills/bundled/agents/memory_manager/SKILL.md +143 -0
  117. package/packages/core/src/skills/bundled/agents/research_and_remember/SKILL.md +139 -0
  118. package/packages/core/src/skills/bundled/agents/task_orchestrator/SKILL.md +98 -0
  119. package/packages/core/src/skills/bundled/api/api_client/SKILL.md +132 -0
  120. package/packages/core/src/skills/bundled/cli/cli_pipeline/SKILL.md +135 -0
  121. package/packages/core/src/skills/bundled/cli/cli_safe_exec/SKILL.md +125 -0
  122. package/packages/core/src/skills/bundled/cli/software_engineering/SKILL.md +23 -0
  123. package/packages/core/src/skills/bundled/cron_manager/SKILL.md +188 -0
  124. package/packages/core/src/skills/bundled/cron_reminder/SKILL.md +112 -0
  125. package/packages/core/src/skills/bundled/filesystem/file_manager/SKILL.md +118 -0
  126. package/packages/core/src/skills/bundled/filesystem/file_read_and_summarize/SKILL.md +109 -0
  127. package/packages/core/src/skills/bundled/filesystem/file_writer/SKILL.md +129 -0
  128. package/packages/core/src/skills/bundled/filesystem/workspace_file_operator/SKILL.md +22 -0
  129. package/packages/core/src/skills/bundled/office/office_document_manager/SKILL.md +262 -0
  130. package/packages/core/src/skills/bundled/search_knowledge/capability_discovery/SKILL.md +75 -0
  131. package/packages/core/src/skills/bundled/web/browser_automate/SKILL.md +120 -0
  132. package/packages/core/src/skills/bundled/web/browser_scrape/SKILL.md +109 -0
  133. package/packages/core/src/skills/bundled/web/web_monitor/SKILL.md +127 -0
  134. package/packages/core/src/skills/bundled/web/web_research/SKILL.md +119 -0
  135. package/packages/core/src/skills/bundled-data.generated.ts +731 -2678
  136. package/packages/core/src/skills/skills.test.ts +52 -11
  137. package/packages/core/src/{harness → storage}/boot-id.ts +5 -2
  138. package/packages/core/src/storage/bootstrap.ts +151 -0
  139. package/packages/core/src/storage/causal-events.ts +84 -0
  140. package/packages/core/src/storage/collections.ts +680 -0
  141. package/packages/core/src/storage/crypto.ts +205 -74
  142. package/packages/core/src/{harness/db-helpers.ts → storage/hive.ts} +63 -7
  143. package/packages/core/src/storage/hivedb.ts +61 -0
  144. package/packages/core/src/storage/index.ts +111 -17
  145. package/packages/core/src/storage/model-id.ts +53 -0
  146. package/packages/core/src/storage/onboarding.ts +540 -972
  147. package/packages/core/src/storage/reconcile.ts +238 -0
  148. package/packages/core/src/storage/seed.ts +572 -406
  149. package/packages/core/src/storage/usage.ts +285 -225
  150. package/packages/core/src/storage/user-email.ts +11 -0
  151. package/packages/core/src/swarm/AgentExecutor.ts +1 -1
  152. package/packages/core/src/swarm/EventBridge.ts +1 -1
  153. package/packages/core/src/swarm/index.ts +12 -9
  154. package/packages/core/src/tool-runtime/index.ts +146 -23
  155. package/packages/core/src/tool-runtime/tool-worker.ts +2 -2
  156. package/packages/core/src/tool-runtime/worker-tools.ts +27 -0
  157. package/packages/core/src/{canvas/a2ui-tools.ts → tools/a2ui/index.ts} +17 -8
  158. package/packages/core/src/tools/agents/get-available-models.ts +36 -54
  159. package/packages/core/src/tools/agents/index.ts +784 -292
  160. package/packages/core/src/tools/api/api-request.test.ts +164 -0
  161. package/packages/core/src/tools/api/api-request.ts +174 -0
  162. package/packages/core/src/tools/api/index.ts +16 -0
  163. package/packages/core/src/tools/cli/index.ts +4 -0
  164. package/packages/core/src/tools/core/index.ts +281 -112
  165. package/packages/core/src/tools/cron/index.ts +121 -124
  166. package/packages/core/src/tools/index.ts +63 -78
  167. package/packages/core/src/tools/office/office-escribir-xlsx.ts +3 -1
  168. package/packages/core/src/tools/types.ts +3 -1
  169. package/packages/core/src/tools/web/artifact-inspect.ts +23 -0
  170. package/packages/core/src/tools/web/browser-screenshot.ts +26 -5
  171. package/packages/core/src/tools/web/browser-service.ts +5 -0
  172. package/packages/core/src/tools/web/browser-type.ts +3 -8
  173. package/packages/core/src/tools/web/index.ts +4 -4
  174. package/packages/core/src/voice/index.ts +89 -63
  175. package/packages/core/src/workers/agent.worker.ts +2 -2
  176. package/packages/core/src/workers/workers.test.ts +3 -10
  177. package/scripts/bump-version.ts +248 -0
  178. package/scripts/generate-skill-bundle.ts +108 -0
  179. package/test/agent-loop-terminal-synthesis.test.ts +32 -0
  180. package/test/catalog-agents-stay-enabled.test.ts +117 -0
  181. package/test/causal-events.test.ts +117 -0
  182. package/test/compaction.test.ts +105 -0
  183. package/test/context-compiler.test.ts +269 -0
  184. package/test/curator.test.ts +130 -0
  185. package/test/durable-queue.test.ts +114 -0
  186. package/test/harness-barrel.test.ts +64 -0
  187. package/test/hive-helpers.test.ts +130 -0
  188. package/test/hivedb-search.test.ts +189 -0
  189. package/test/internal-turns.test.ts +166 -0
  190. package/test/job-idempotency.test.ts +68 -0
  191. package/test/job-retry-backoff.test.ts +184 -0
  192. package/test/job-store.test.ts +381 -0
  193. package/test/llm-retry.test.ts +97 -0
  194. package/test/memory-perf.test.ts +774 -0
  195. package/test/minimal-loadout.test.ts +78 -0
  196. package/test/model-catalog.test.ts +105 -0
  197. package/test/preload.ts +12 -0
  198. package/test/reflector.test.ts +320 -0
  199. package/test/retention-cap.test.ts +91 -0
  200. package/test/retired-capabilities-pruned.test.ts +192 -0
  201. package/test/run-store.test.ts +355 -0
  202. package/test/scratchpad.test.ts +74 -0
  203. package/test/secrets-durability.test.ts +119 -0
  204. package/test/seed-model-reseed.test.ts +155 -0
  205. package/test/setup-agent-seed.test.ts +264 -0
  206. package/test/tool-inventory.test.ts +65 -0
  207. package/test/tool-runtime.test.ts +258 -0
  208. package/test/toon.test.ts +429 -0
  209. package/tsconfig.json +2 -0
  210. package/packages/core/src/ace/Curator.ts +0 -158
  211. package/packages/core/src/ace/Reflector.ts +0 -200
  212. package/packages/core/src/ace/index.ts +0 -4
  213. package/packages/core/src/agent/AgentRunner.ts +0 -711
  214. package/packages/core/src/agent/ContextCompiler.ts +0 -567
  215. package/packages/core/src/agent/ContextGuard.ts +0 -91
  216. package/packages/core/src/agent/ConversationStore.ts +0 -254
  217. package/packages/core/src/agent/Hooks.ts +0 -166
  218. package/packages/core/src/agent/StuckLoop.ts +0 -133
  219. package/packages/core/src/agent/providers/LLMClient.ts +0 -149
  220. package/packages/core/src/agent/providers/anthropic.ts +0 -212
  221. package/packages/core/src/agent/providers/openai-compat.ts +0 -231
  222. package/packages/core/src/agent/selectors/PlaybookSelector.ts +0 -121
  223. package/packages/core/src/agent/selectors/SkillSelector.ts +0 -322
  224. package/packages/core/src/agent/selectors/index.ts +0 -6
  225. package/packages/core/src/auth/auth.ts +0 -121
  226. package/packages/core/src/auth/index.ts +0 -1
  227. package/packages/core/src/canvas/CanvasManager.ts +0 -390
  228. package/packages/core/src/canvas/canvas-tools.ts +0 -448
  229. package/packages/core/src/harness/collections.ts +0 -98
  230. package/packages/core/src/harness/goal-verifier.ts +0 -141
  231. package/packages/core/src/harness/harness.test.ts +0 -236
  232. package/packages/core/src/harness/reconcile.ts +0 -149
  233. package/packages/core/src/mcp/MCPToolAdapter.ts +0 -176
  234. package/packages/core/src/multimodal/VisionService.ts +0 -293
  235. package/packages/core/src/scheduler/dag/AgentExecutor.ts +0 -53
  236. package/packages/core/src/scheduler/dag/DAGScheduler.ts +0 -250
  237. package/packages/core/src/scheduler/dag/EventBridge.ts +0 -122
  238. package/packages/core/src/scheduler/dag/TaskGraph.ts +0 -192
  239. package/packages/core/src/scheduler/dag/TaskNode.ts +0 -97
  240. package/packages/core/src/scheduler/dag/TaskResult.ts +0 -22
  241. package/packages/core/src/scheduler/dag/errors.ts +0 -37
  242. package/packages/core/src/scheduler/dag/index.ts +0 -26
  243. package/packages/core/src/scheduler/dag/presets/ResearchPreset.ts +0 -97
  244. package/packages/core/src/scheduler/dag/strategies/ParallelStrategy.ts +0 -21
  245. package/packages/core/src/scheduler/dag/strategies/PriorityStrategy.ts +0 -46
  246. package/packages/core/src/storage/HiveDBStorage.ts +0 -64
  247. package/packages/core/src/storage/SQLiteStorage.ts +0 -414
  248. package/packages/core/src/storage/hiveSeed.ts +0 -308
  249. package/packages/core/src/storage/hiveStorage.test.ts +0 -38
  250. package/packages/core/src/storage/schema.ts +0 -689
  251. package/packages/core/src/storage/storage.test.ts +0 -37
  252. package/packages/core/src/swarm/AgentBus.ts +0 -460
  253. package/packages/core/src/swarm/EventBus.ts +0 -169
  254. package/packages/core/src/swarm/WorkerPool.ts +0 -236
  255. package/packages/core/src/tools/bridge-events.ts +0 -26
  256. package/packages/core/src/tools/canvas/index.ts +0 -375
  257. package/packages/core/src/tools/codebridge/index.ts +0 -342
  258. package/packages/core/src/tools/meeting/index.ts +0 -353
  259. package/packages/core/src/tools/projects/index.ts +0 -37
  260. package/packages/core/src/tools/projects/project-create.ts +0 -94
  261. package/packages/core/src/tools/projects/project-done.ts +0 -66
  262. package/packages/core/src/tools/projects/project-fail.ts +0 -66
  263. package/packages/core/src/tools/projects/project-list.ts +0 -96
  264. package/packages/core/src/tools/projects/project-update.ts +0 -72
  265. package/packages/core/src/tools/projects/task-create.ts +0 -68
  266. package/packages/core/src/tools/projects/task-evaluate.ts +0 -93
  267. package/packages/core/src/tools/projects/task-update.ts +0 -93
  268. package/packages/core/src/tools/voice/index.ts +0 -104
  269. package/packages/core/src/tools/web/api-request.test.ts +0 -170
  270. package/packages/core/src/tools/web/api-request.ts +0 -239
  271. package/test/setup-db.ts +0 -216
  272. /package/packages/core/src/agent/{NativeTools.ts → native-tools.ts} +0 -0
@@ -1,390 +0,0 @@
1
- import { EventEmitter } from "events";
2
- import { logger } from "../utils/logger.ts";
3
- import { eventBus } from "../swarm/EventBus.ts";
4
-
5
- export interface WebSocketLike {
6
- readyState: number;
7
- send(data: string | ArrayBuffer | Uint8Array): number | boolean;
8
- close(code?: number, reason?: string): void;
9
- }
10
-
11
- export const WebSocketState = {
12
- CONNECTING: 0,
13
- OPEN: 1,
14
- CLOSING: 2,
15
- CLOSED: 3,
16
- };
17
-
18
- export interface CanvasComponent {
19
- id: string;
20
- type: "button" | "form" | "chart" | "table" | "markdown" | "text" | "image" | "card" | "progress" | "list" | "confirm";
21
- props: Record<string, unknown>;
22
- span?: "full" | "half";
23
- }
24
-
25
- export interface CanvasMessage {
26
- type: "canvas:render" | "canvas:update" | "canvas:clear" | "canvas:interact" | "canvas:connected" | "canvas:snapshot";
27
- sessionId: string;
28
- componentId?: string;
29
- component?: CanvasComponent;
30
- action?: string;
31
- data?: unknown;
32
- }
33
-
34
- export interface InteractionEvent {
35
- sessionId: string;
36
- componentId: string;
37
- action: string;
38
- data: unknown;
39
- }
40
-
41
- interface PendingInteraction {
42
- resolve: (data: unknown) => void;
43
- reject: (error: Error) => void;
44
- timeoutId: ReturnType<typeof setTimeout>;
45
- }
46
-
47
- interface A2UISurfaceCache {
48
- createData: Record<string, unknown>;
49
- components?: unknown[];
50
- dataModel?: Record<string, unknown>;
51
- }
52
-
53
- export class CanvasManager extends EventEmitter {
54
- private sessions: Map<string, WebSocketLike> = new Map();
55
- private pendingInteractions: Map<string, PendingInteraction> = new Map();
56
- private componentCache: Map<string, CanvasComponent[]> = new Map();
57
- private a2uiCache: Map<string, A2UISurfaceCache> = new Map();
58
- private log = logger.child("canvas");
59
- private heartbeatInterval: ReturnType<typeof setInterval> | null = null;
60
-
61
- constructor() {
62
- super();
63
- this.startHeartbeat();
64
- }
65
-
66
- private startHeartbeat(): void {
67
- if (this.heartbeatInterval) return;
68
-
69
- // Enviar ping a todas las sesiones cada 30 segundos
70
- this.heartbeatInterval = setInterval(() => {
71
- for (const [sessionId, ws] of this.sessions) {
72
- if (ws.readyState === WebSocketState.OPEN) {
73
- try {
74
- ws.send(JSON.stringify({ type: "canvas:ping", sessionId }));
75
- this.log.debug(`Heartbeat sent to ${sessionId}`);
76
- } catch (e) {
77
- this.log.error(`Failed to send heartbeat to ${sessionId}`);
78
- }
79
- }
80
- }
81
- }, 30000);
82
- }
83
-
84
- private stopHeartbeat(): void {
85
- if (this.heartbeatInterval) {
86
- clearInterval(this.heartbeatInterval);
87
- this.heartbeatInterval = null;
88
- }
89
- }
90
-
91
- registerSession(sessionId: string, ws: WebSocketLike): void {
92
- this.sessions.set(sessionId, ws);
93
- this.log.info(`Canvas session registered: ${sessionId}`);
94
-
95
- eventBus.emit("tool:completed" as any, {
96
- toolName: "canvas:session:register",
97
- result: { sessionId },
98
- duration: 0,
99
- success: true,
100
- });
101
-
102
- // Notify the client that the session is registered
103
- ws.send(JSON.stringify({ type: "canvas:connected", sessionId }));
104
-
105
- // Replay any cached A2UI surfaces so late-connecting clients get current state
106
- for (const [surfaceId, cache] of this.a2uiCache) {
107
- try {
108
- ws.send(JSON.stringify({ type: "a2ui:createSurface", data: cache.createData }));
109
- if (cache.components && cache.components.length > 0) {
110
- ws.send(JSON.stringify({ type: "a2ui:updateComponents", data: { surfaceId, components: cache.components } }));
111
- }
112
- if (cache.dataModel && Object.keys(cache.dataModel).length > 0) {
113
- ws.send(JSON.stringify({ type: "a2ui:updateDataModel", data: { surfaceId, path: undefined, value: cache.dataModel } }));
114
- }
115
- this.log.debug(`Replayed A2UI surface '${surfaceId}' to session ${sessionId}`);
116
- } catch (e) {
117
- this.log.warn(`Failed to replay A2UI surface '${surfaceId}' to ${sessionId}`);
118
- }
119
- }
120
- }
121
-
122
- unregisterSession(sessionId: string): void {
123
- this.sessions.delete(sessionId);
124
- this.cleanupPendingInteractions(sessionId);
125
- this.log.info(`Canvas session disconnected: ${sessionId}`);
126
- }
127
-
128
- handleMessage(sessionId: string, data: unknown): void {
129
- try {
130
- const msg = JSON.parse(data as string);
131
-
132
- this.log.debug(`Received message from session ${sessionId}: ${msg.type}`);
133
-
134
- // === HANDSHAKE INICIAL ===
135
- if (msg.type === "canvas:handshake") {
136
- this.log.debug(`Handshake received from ${sessionId}`, msg);
137
- const ws = this.sessions.get(sessionId);
138
-
139
- // Confirmar handshake
140
- if (ws && ws.readyState === WebSocketState.OPEN) {
141
- ws.send(JSON.stringify({
142
- type: "canvas:handshake:ack",
143
- sessionId,
144
- serverSessionId: sessionId,
145
- timestamp: Date.now()
146
- }));
147
- }
148
- return;
149
- }
150
-
151
- // === HEARTBEAT: PING DEL CLIENTE ===
152
- if (msg.type === "canvas:ping") {
153
- const ws = this.sessions.get(sessionId);
154
- if (ws && ws.readyState === WebSocketState.OPEN) {
155
- ws.send(JSON.stringify({ type: "canvas:pong", sessionId }));
156
- }
157
- return;
158
- }
159
-
160
- // === PONG DEL CLIENTE (opcional) ===
161
- if (msg.type === "canvas:pong") {
162
- // El cliente respondió al ping del servidor - conexión viva
163
- this.log.debug(`Pong received from ${sessionId}`);
164
- return;
165
- }
166
-
167
- if (msg.type === "canvas:interact") {
168
- const { componentId, action, data: interactionData } = msg.payload || msg;
169
-
170
- if (componentId) {
171
- this.resolveInteraction(sessionId, componentId, interactionData);
172
- }
173
-
174
- this.emit("interaction", {
175
- sessionId,
176
- componentId,
177
- action,
178
- data: interactionData,
179
- } as InteractionEvent);
180
- } else if (msg.type === "canvas:get_cached") {
181
- const components = this.getSessionComponents(sessionId);
182
- const ws = this.sessions.get(sessionId);
183
-
184
- this.log.info(`Sending ${components.length} cached components to session ${sessionId}`);
185
-
186
- if (ws && ws.readyState === WebSocketState.OPEN) {
187
- for (const component of components) {
188
- ws.send(JSON.stringify({
189
- type: "canvas:render",
190
- sessionId,
191
- component
192
- }));
193
- }
194
- }
195
- }
196
- } catch (error) {
197
- this.log.error(`Invalid canvas message: ${(error as Error).message}`);
198
- }
199
- }
200
-
201
- private resolveInteraction(sessionId: string, componentId: string, data: unknown): void {
202
- const key = `${sessionId}:${componentId}`;
203
- const pending = this.pendingInteractions.get(key);
204
-
205
- if (pending) {
206
- clearTimeout(pending.timeoutId);
207
- this.pendingInteractions.delete(key);
208
-
209
- // Remove from cache so it doesn't reappear in the next snapshot
210
- const cached = this.componentCache.get(sessionId);
211
- if (cached) {
212
- this.componentCache.set(sessionId, cached.filter(c => c.id !== componentId));
213
- }
214
-
215
- pending.resolve(data);
216
- }
217
- }
218
-
219
- async render(sessionId: string, component: CanvasComponent): Promise<void> {
220
- // Always cache the component for later retrieval
221
- const cached = this.componentCache.get(sessionId) || [];
222
- const existingIdx = cached.findIndex(c => c.id === component.id);
223
- if (existingIdx >= 0) {
224
- cached[existingIdx] = component;
225
- } else {
226
- cached.push(component);
227
- }
228
- this.componentCache.set(sessionId, cached);
229
-
230
- const ws = this.sessions.get(sessionId);
231
-
232
- if (!ws || ws.readyState !== WebSocketState.OPEN) {
233
- // Session not connected, but we cached the component
234
- this.log.debug(`Session ${sessionId} NOT connected. Available: ${this.getConnectedSessions().join(", ")}`);
235
- return;
236
- }
237
-
238
- const message: CanvasMessage = {
239
- type: "canvas:render",
240
- sessionId,
241
- component,
242
- };
243
-
244
- this.log.debug(`Sending render to session ${sessionId}: ${component.id}`);
245
- ws.send(JSON.stringify(message));
246
- this.log.debug(`Rendered component ${component.id} to session ${sessionId}`);
247
- }
248
-
249
- async update(sessionId: string, component: CanvasComponent): Promise<void> {
250
- const ws = this.sessions.get(sessionId);
251
-
252
- if (!ws || ws.readyState !== WebSocketState.OPEN) {
253
- throw new Error(`Session not connected: ${sessionId}`);
254
- }
255
-
256
- const message: CanvasMessage = {
257
- type: "canvas:update",
258
- sessionId,
259
- component,
260
- };
261
-
262
- ws.send(JSON.stringify(message));
263
- this.log.debug(`Updated component ${component.id} in session ${sessionId}`);
264
- }
265
-
266
- async clear(sessionId: string): Promise<void> {
267
- const ws = this.sessions.get(sessionId);
268
-
269
- if (!ws || ws.readyState !== WebSocketState.OPEN) {
270
- throw new Error(`Session not connected: ${sessionId}`);
271
- }
272
-
273
- const message: CanvasMessage = {
274
- type: "canvas:clear",
275
- sessionId,
276
- };
277
-
278
- ws.send(JSON.stringify(message));
279
- this.log.debug(`Cleared canvas for session ${sessionId}`);
280
- }
281
-
282
- async sendA2UIMessage(sessionId: string, messageType: string, data: Record<string, unknown>): Promise<void> {
283
- // Update A2UI cache so late-connecting clients can receive current state
284
- const surfaceId = data.surfaceId as string | undefined;
285
- if (surfaceId) {
286
- if (messageType === "a2ui:createSurface") {
287
- this.a2uiCache.set(surfaceId, { createData: data });
288
- } else if (messageType === "a2ui:updateComponents") {
289
- const cached = this.a2uiCache.get(surfaceId);
290
- if (cached) cached.components = data.components as unknown[];
291
- } else if (messageType === "a2ui:updateDataModel") {
292
- const cached = this.a2uiCache.get(surfaceId);
293
- if (cached) {
294
- const path = data.path as string | undefined;
295
- const value = data.value as Record<string, unknown>;
296
- if (!path || path === "/") {
297
- cached.dataModel = value;
298
- } else {
299
- cached.dataModel = cached.dataModel ?? {};
300
- // Store the full model snapshot when possible; partial paths accumulate
301
- const key = path.replace(/^\//, "").split("/")[0];
302
- if (key) cached.dataModel[key] = value;
303
- }
304
- }
305
- } else if (messageType === "a2ui:deleteSurface") {
306
- this.a2uiCache.delete(surfaceId);
307
- }
308
- }
309
-
310
- const ws = this.sessions.get(sessionId);
311
-
312
- if (!ws || ws.readyState !== WebSocketState.OPEN) {
313
- const connected = this.getConnectedSessions();
314
- this.log.warn(`Session ${sessionId} NOT connected for A2UI message. Cached for replay. Available: ${connected.join(", ")}`);
315
- return;
316
- }
317
-
318
- ws.send(JSON.stringify({ type: messageType, data }));
319
- this.log.debug(`Sent A2UI message '${messageType}' to session ${sessionId}`);
320
- }
321
-
322
- async waitForInteraction(
323
- sessionId: string,
324
- componentId: string,
325
- timeout = 300000
326
- ): Promise<unknown> {
327
- const key = `${sessionId}:${componentId}`;
328
-
329
- return new Promise((resolve, reject) => {
330
- const timeoutId = setTimeout(() => {
331
- this.pendingInteractions.delete(key);
332
- reject(new Error(`Interaction timeout for ${componentId}`));
333
- }, timeout);
334
-
335
- this.pendingInteractions.set(key, { resolve, reject, timeoutId });
336
- });
337
- }
338
-
339
- handleInteraction(sessionId: string, componentId: string, data: unknown): void {
340
- this.resolveInteraction(sessionId, componentId, data);
341
- }
342
-
343
- getSessionComponents(sessionId: string): CanvasComponent[] {
344
- return this.componentCache.get(sessionId) || [];
345
- }
346
-
347
- isSessionConnected(sessionId: string): boolean {
348
- const ws = this.sessions.get(sessionId);
349
- return ws !== undefined && ws.readyState === WebSocketState.OPEN;
350
- }
351
-
352
- getConnectedSessions(): string[] {
353
- return Array.from(this.sessions.entries())
354
- .filter(([_, ws]) => ws.readyState === WebSocketState.OPEN)
355
- .map(([id]) => id);
356
- }
357
-
358
- getStats(): { totalSessions: number; activeSessions: number; pendingInteractions: number } {
359
- return {
360
- totalSessions: this.sessions.size,
361
- activeSessions: this.getConnectedSessions().length,
362
- pendingInteractions: this.pendingInteractions.size,
363
- };
364
- }
365
-
366
- private cleanupPendingInteractions(sessionId: string): void {
367
- for (const [key, pending] of this.pendingInteractions) {
368
- if (key.startsWith(`${sessionId}:`)) {
369
- clearTimeout(pending.timeoutId);
370
- pending.reject(new Error(`Session disconnected: ${sessionId}`));
371
- this.pendingInteractions.delete(key);
372
- }
373
- }
374
- }
375
-
376
- clearAll(): void {
377
- this.stopHeartbeat();
378
-
379
- for (const pending of this.pendingInteractions.values()) {
380
- clearTimeout(pending.timeoutId);
381
- pending.reject(new Error("Canvas manager cleared"));
382
- }
383
-
384
- this.pendingInteractions.clear();
385
- this.sessions.clear();
386
- this.log.info("Canvas manager cleared");
387
- }
388
- }
389
-
390
- export const canvasManager = new CanvasManager();