@johpaz/hive-sdk 0.1.4 → 0.1.6

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 (278) hide show
  1. package/CHANGELOG.md +129 -0
  2. package/README.md +78 -23
  3. package/bun.lock +55 -29
  4. package/bunfig.toml +4 -2
  5. package/docs/API-AGENTS.md +78 -27
  6. package/docs/API-CONTEXT-COMPILER.md +31 -34
  7. package/docs/API-TOOLS-SKILLS-CHANNELS.md +58 -22
  8. package/docs/HIVE-HARNESS.md +1 -1
  9. package/docs/INDEX.md +4 -4
  10. package/docs/TEMPLATE-HIVE-APP.md +10 -10
  11. package/package.json +17 -12
  12. package/packages/cli/package.json +2 -2
  13. package/packages/cli/src/commands/create-app.test.ts +36 -7
  14. package/packages/cli/src/commands/init.ts +3 -3
  15. package/packages/cli/src/commands/run.ts +1 -1
  16. package/packages/cli/src/commands/test.ts +37 -25
  17. package/packages/cli/src/commands/trace.ts +30 -28
  18. package/packages/cli/templates/hive-app/.env.example +10 -2
  19. package/packages/cli/templates/hive-app/README.md +103 -0
  20. package/packages/cli/templates/hive-app/hive.config.ts +9 -3
  21. package/packages/cli/templates/hive-app/src/agents/coordinator.ts +8 -1
  22. package/packages/cli/templates/hive-app/src/main.ts +12 -19
  23. package/packages/core/package.json +13 -12
  24. package/packages/core/src/agent/acceptance-checks.ts +172 -0
  25. package/packages/core/src/agent/agent-catalog.ts +348 -0
  26. package/packages/core/src/agent/agent-loop.ts +1373 -0
  27. package/packages/core/src/agent/capability-search.ts +186 -0
  28. package/packages/core/src/agent/catalog-selector.ts +103 -0
  29. package/packages/core/src/agent/{Compaction.ts → compaction.ts} +86 -63
  30. package/packages/core/src/agent/context-compiler.ts +689 -0
  31. package/packages/core/src/agent/conversation-store.ts +381 -0
  32. package/packages/core/src/agent/curator.ts +276 -0
  33. package/packages/core/src/agent/delegation-runtime.ts +241 -0
  34. package/packages/core/src/agent/goal-runner.ts +323 -0
  35. package/packages/core/src/agent/index.ts +17 -12
  36. package/packages/core/src/agent/llm-client.ts +266 -0
  37. package/packages/core/src/agent/llm-providers/anthropic.ts +264 -0
  38. package/packages/core/src/agent/llm-providers/deepseek.ts +8 -0
  39. package/packages/core/src/agent/{providers → llm-providers}/gemini.ts +98 -60
  40. package/packages/core/src/agent/llm-providers/groq.ts +5 -0
  41. package/packages/core/src/agent/llm-providers/hiveagents.ts +253 -0
  42. package/packages/core/src/agent/{providers → llm-providers}/interface.ts +73 -13
  43. package/packages/core/src/agent/llm-providers/kimi.ts +8 -0
  44. package/packages/core/src/agent/llm-providers/minimax.ts +13 -0
  45. package/packages/core/src/agent/llm-providers/mistral.ts +5 -0
  46. package/packages/core/src/agent/llm-providers/modelscope.ts +5 -0
  47. package/packages/core/src/agent/llm-providers/nvidia.ts +5 -0
  48. package/packages/core/src/agent/{providers → llm-providers}/ollama.ts +31 -5
  49. package/packages/core/src/agent/llm-providers/openai-compat-base.ts +418 -0
  50. package/packages/core/src/agent/llm-providers/openai.ts +5 -0
  51. package/packages/core/src/agent/llm-providers/opencode-go.ts +9 -0
  52. package/packages/core/src/agent/llm-providers/openrouter.ts +5 -0
  53. package/packages/core/src/agent/llm-providers/qwen.ts +5 -0
  54. package/packages/core/src/agent/llm-providers/z-ai.ts +5 -0
  55. package/packages/core/src/agent/minimal-loadout.ts +47 -0
  56. package/packages/core/src/agent/playbook-selector.ts +119 -0
  57. package/packages/core/src/agent/{PromptBuilder.ts → prompt-builder.ts} +21 -22
  58. package/packages/core/src/{harness → agent}/proof-packet.ts +16 -21
  59. package/packages/core/src/agent/providers/index.ts +35 -16
  60. package/packages/core/src/agent/reflector.ts +320 -0
  61. package/packages/core/src/agent/routing-intent.ts +22 -0
  62. package/packages/core/src/{harness → agent}/run-epoch.ts +4 -3
  63. package/packages/core/src/{harness → agent}/run-store.ts +142 -81
  64. package/packages/core/src/agent/{Service.ts → service.ts} +37 -26
  65. package/packages/core/src/agent/skill-selector.ts +374 -0
  66. package/packages/core/src/agent/stuck-loop.ts +209 -0
  67. package/packages/core/src/agent/{selectors/ToolSelector.ts → tool-selector.ts} +188 -178
  68. package/packages/core/src/{ace/Tracer.ts → agent/tracer.ts} +37 -27
  69. package/packages/core/src/api/createAgent.test.ts +139 -27
  70. package/packages/core/src/api/createAgent.ts +232 -44
  71. package/packages/core/src/artifacts/store.ts +162 -0
  72. package/packages/core/src/canvas/canvas-manager.ts +161 -0
  73. package/packages/core/src/canvas/canvas.test.ts +8 -4
  74. package/packages/core/src/canvas/emitter.ts +131 -80
  75. package/packages/core/src/canvas/index.ts +1 -3
  76. package/packages/core/src/channels/base.ts +9 -1
  77. package/packages/core/src/channels/discord.ts +5 -4
  78. package/packages/core/src/channels/manager.ts +122 -30
  79. package/packages/core/src/channels/slack.ts +5 -4
  80. package/packages/core/src/channels/telegram.ts +36 -6
  81. package/packages/core/src/channels/webchat.ts +11 -10
  82. package/packages/core/src/channels/whatsapp.ts +23 -7
  83. package/packages/core/src/config/index.ts +13 -2
  84. package/packages/core/src/config/loader.ts +76 -29
  85. package/packages/core/src/ethics/EthicsGuard.test.ts +90 -36
  86. package/packages/core/src/ethics/EthicsGuard.ts +51 -47
  87. package/packages/core/src/events/agent-bus.ts +44 -68
  88. package/packages/core/src/events/channel-narration.ts +150 -0
  89. package/packages/core/src/events/narration.ts +82 -0
  90. package/packages/core/src/events/tool-narration.ts +62 -0
  91. package/packages/core/src/gateway/delegation-groups.ts +258 -0
  92. package/packages/core/src/{harness → gateway}/durable-queue.ts +102 -42
  93. package/packages/core/src/{harness → gateway}/job-store.ts +85 -48
  94. package/packages/core/src/gateway/lane-queue.ts +173 -0
  95. package/packages/core/src/gateway/notification-inbox.ts +57 -0
  96. package/packages/core/src/gateway/server.ts +1 -1
  97. package/packages/core/src/harness/index.ts +46 -27
  98. package/packages/core/src/index.ts +33 -27
  99. package/packages/core/src/mcp/hot-reload.ts +32 -23
  100. package/packages/core/src/mcp/index.ts +6 -3
  101. package/packages/core/src/mcp/singleton.ts +1 -4
  102. package/packages/core/src/mcp/tool-sync.ts +138 -0
  103. package/packages/core/src/memory/Scratchpad.test.ts +39 -20
  104. package/packages/core/src/memory/Scratchpad.ts +27 -34
  105. package/packages/core/src/multimodal/vision-service.ts +44 -38
  106. package/packages/core/src/resilience/retry.ts +95 -0
  107. package/packages/core/src/scheduler/CronScheduler.ts +334 -287
  108. package/packages/core/src/scheduler/index.ts +9 -7
  109. package/packages/core/src/scheduler/integration.ts +46 -26
  110. package/packages/core/src/scheduler/scheduler.test.ts +9 -13
  111. package/packages/core/src/scheduler/types.ts +7 -2
  112. package/packages/core/src/security/Pairing.ts +1 -1
  113. package/packages/core/src/skills/bundled/a2ui/a2ui_dashboard/SKILL.md +176 -0
  114. package/packages/core/src/skills/bundled/a2ui/a2ui_form/SKILL.md +202 -0
  115. package/packages/core/src/skills/bundled/a2ui/a2ui_interactive/SKILL.md +206 -0
  116. package/packages/core/src/skills/bundled/agents/agent_spawner/SKILL.md +173 -0
  117. package/packages/core/src/skills/bundled/agents/memory_manager/SKILL.md +143 -0
  118. package/packages/core/src/skills/bundled/agents/research_and_remember/SKILL.md +139 -0
  119. package/packages/core/src/skills/bundled/agents/task_orchestrator/SKILL.md +98 -0
  120. package/packages/core/src/skills/bundled/api/api_client/SKILL.md +132 -0
  121. package/packages/core/src/skills/bundled/cli/cli_pipeline/SKILL.md +135 -0
  122. package/packages/core/src/skills/bundled/cli/cli_safe_exec/SKILL.md +125 -0
  123. package/packages/core/src/skills/bundled/cli/software_engineering/SKILL.md +23 -0
  124. package/packages/core/src/skills/bundled/cron_manager/SKILL.md +188 -0
  125. package/packages/core/src/skills/bundled/cron_reminder/SKILL.md +112 -0
  126. package/packages/core/src/skills/bundled/filesystem/file_manager/SKILL.md +118 -0
  127. package/packages/core/src/skills/bundled/filesystem/file_read_and_summarize/SKILL.md +109 -0
  128. package/packages/core/src/skills/bundled/filesystem/file_writer/SKILL.md +129 -0
  129. package/packages/core/src/skills/bundled/filesystem/workspace_file_operator/SKILL.md +22 -0
  130. package/packages/core/src/skills/bundled/office/office_document_manager/SKILL.md +262 -0
  131. package/packages/core/src/skills/bundled/search_knowledge/capability_discovery/SKILL.md +75 -0
  132. package/packages/core/src/skills/bundled/web/browser_automate/SKILL.md +120 -0
  133. package/packages/core/src/skills/bundled/web/browser_scrape/SKILL.md +109 -0
  134. package/packages/core/src/skills/bundled/web/web_monitor/SKILL.md +127 -0
  135. package/packages/core/src/skills/bundled/web/web_research/SKILL.md +119 -0
  136. package/packages/core/src/skills/bundled-data.generated.ts +731 -2678
  137. package/packages/core/src/skills/skills.test.ts +52 -11
  138. package/packages/core/src/{harness → storage}/boot-id.ts +5 -2
  139. package/packages/core/src/storage/bootstrap.ts +151 -0
  140. package/packages/core/src/storage/causal-events.ts +84 -0
  141. package/packages/core/src/storage/collections.ts +680 -0
  142. package/packages/core/src/storage/crypto.ts +205 -74
  143. package/packages/core/src/{harness/db-helpers.ts → storage/hive.ts} +63 -7
  144. package/packages/core/src/storage/hivedb.ts +61 -0
  145. package/packages/core/src/storage/index.ts +111 -18
  146. package/packages/core/src/storage/model-id.ts +53 -0
  147. package/packages/core/src/storage/onboarding.ts +540 -972
  148. package/packages/core/src/storage/reconcile.ts +238 -0
  149. package/packages/core/src/storage/seed.ts +572 -406
  150. package/packages/core/src/storage/usage.ts +285 -225
  151. package/packages/core/src/storage/user-email.ts +11 -0
  152. package/packages/core/src/swarm/AgentExecutor.ts +1 -1
  153. package/packages/core/src/swarm/EventBridge.ts +1 -1
  154. package/packages/core/src/swarm/index.ts +12 -9
  155. package/packages/core/src/tool-runtime/index.ts +146 -23
  156. package/packages/core/src/tool-runtime/tool-worker.ts +2 -2
  157. package/packages/core/src/tool-runtime/worker-tools.ts +27 -0
  158. package/packages/core/src/{canvas/a2ui-tools.ts → tools/a2ui/index.ts} +17 -8
  159. package/packages/core/src/tools/agents/get-available-models.ts +36 -54
  160. package/packages/core/src/tools/agents/index.ts +784 -292
  161. package/packages/core/src/tools/api/api-request.test.ts +164 -0
  162. package/packages/core/src/tools/api/api-request.ts +174 -0
  163. package/packages/core/src/tools/api/index.ts +16 -0
  164. package/packages/core/src/tools/cli/index.ts +4 -0
  165. package/packages/core/src/tools/core/index.ts +281 -112
  166. package/packages/core/src/tools/cron/index.ts +121 -124
  167. package/packages/core/src/tools/index.ts +63 -78
  168. package/packages/core/src/tools/office/office-escribir-xlsx.ts +3 -1
  169. package/packages/core/src/tools/types.ts +3 -1
  170. package/packages/core/src/tools/web/artifact-inspect.ts +23 -0
  171. package/packages/core/src/tools/web/browser-backend.ts +129 -0
  172. package/packages/core/src/tools/web/browser-screenshot.ts +26 -5
  173. package/packages/core/src/tools/web/browser-service.ts +80 -35
  174. package/packages/core/src/tools/web/browser-type.ts +3 -8
  175. package/packages/core/src/tools/web/index.ts +4 -4
  176. package/packages/core/src/tools/web/webview-backend.ts +412 -0
  177. package/packages/core/src/voice/index.ts +89 -63
  178. package/packages/core/src/workers/agent.worker.ts +2 -2
  179. package/packages/core/src/workers/workers.test.ts +3 -10
  180. package/scripts/bump-version.ts +248 -0
  181. package/scripts/generate-skill-bundle.ts +108 -0
  182. package/test/acceptance-checks.test.ts +403 -0
  183. package/test/agent-loop-terminal-synthesis.test.ts +32 -0
  184. package/test/browser-backend.test.ts +308 -0
  185. package/test/catalog-agents-stay-enabled.test.ts +117 -0
  186. package/test/causal-events.test.ts +117 -0
  187. package/test/compaction.test.ts +105 -0
  188. package/test/context-compiler.test.ts +269 -0
  189. package/test/curator.test.ts +130 -0
  190. package/test/durable-queue.test.ts +114 -0
  191. package/test/harness-barrel.test.ts +64 -0
  192. package/test/hive-helpers.test.ts +130 -0
  193. package/test/hivedb-search.test.ts +189 -0
  194. package/test/internal-turns.test.ts +166 -0
  195. package/test/job-idempotency.test.ts +68 -0
  196. package/test/job-retry-backoff.test.ts +184 -0
  197. package/test/job-store.test.ts +381 -0
  198. package/test/llm-retry.test.ts +97 -0
  199. package/test/memory-perf.test.ts +774 -0
  200. package/test/minimal-loadout.test.ts +78 -0
  201. package/test/model-catalog.test.ts +105 -0
  202. package/test/preload.ts +12 -0
  203. package/test/reflector.test.ts +320 -0
  204. package/test/retention-cap.test.ts +91 -0
  205. package/test/retired-capabilities-pruned.test.ts +192 -0
  206. package/test/run-store.test.ts +355 -0
  207. package/test/scratchpad.test.ts +74 -0
  208. package/test/secrets-durability.test.ts +119 -0
  209. package/test/seed-model-reseed.test.ts +155 -0
  210. package/test/setup-agent-seed.test.ts +264 -0
  211. package/test/tool-inventory.test.ts +65 -0
  212. package/test/tool-runtime.test.ts +258 -0
  213. package/test/tool-selector-runtime-tools.test.ts +117 -0
  214. package/test/toon.test.ts +429 -0
  215. package/tsconfig.json +2 -0
  216. package/packages/core/src/ace/Curator.ts +0 -158
  217. package/packages/core/src/ace/Reflector.ts +0 -200
  218. package/packages/core/src/ace/index.ts +0 -4
  219. package/packages/core/src/agent/AgentRunner.ts +0 -711
  220. package/packages/core/src/agent/ContextCompiler.ts +0 -567
  221. package/packages/core/src/agent/ContextGuard.ts +0 -91
  222. package/packages/core/src/agent/ConversationStore.ts +0 -254
  223. package/packages/core/src/agent/Hooks.ts +0 -166
  224. package/packages/core/src/agent/StuckLoop.ts +0 -133
  225. package/packages/core/src/agent/providers/LLMClient.ts +0 -149
  226. package/packages/core/src/agent/providers/anthropic.ts +0 -212
  227. package/packages/core/src/agent/providers/openai-compat.ts +0 -231
  228. package/packages/core/src/agent/selectors/PlaybookSelector.ts +0 -121
  229. package/packages/core/src/agent/selectors/SkillSelector.ts +0 -322
  230. package/packages/core/src/agent/selectors/index.ts +0 -6
  231. package/packages/core/src/auth/auth.ts +0 -121
  232. package/packages/core/src/auth/index.ts +0 -1
  233. package/packages/core/src/canvas/CanvasManager.ts +0 -390
  234. package/packages/core/src/canvas/canvas-tools.ts +0 -448
  235. package/packages/core/src/harness/collections.ts +0 -98
  236. package/packages/core/src/harness/goal-verifier.ts +0 -141
  237. package/packages/core/src/harness/harness.test.ts +0 -236
  238. package/packages/core/src/harness/reconcile.ts +0 -149
  239. package/packages/core/src/mcp/MCPToolAdapter.ts +0 -176
  240. package/packages/core/src/multimodal/VisionService.ts +0 -293
  241. package/packages/core/src/scheduler/dag/AgentExecutor.ts +0 -53
  242. package/packages/core/src/scheduler/dag/DAGScheduler.ts +0 -250
  243. package/packages/core/src/scheduler/dag/EventBridge.ts +0 -122
  244. package/packages/core/src/scheduler/dag/TaskGraph.ts +0 -192
  245. package/packages/core/src/scheduler/dag/TaskNode.ts +0 -97
  246. package/packages/core/src/scheduler/dag/TaskResult.ts +0 -22
  247. package/packages/core/src/scheduler/dag/errors.ts +0 -37
  248. package/packages/core/src/scheduler/dag/index.ts +0 -26
  249. package/packages/core/src/scheduler/dag/presets/ResearchPreset.ts +0 -97
  250. package/packages/core/src/scheduler/dag/strategies/ParallelStrategy.ts +0 -21
  251. package/packages/core/src/scheduler/dag/strategies/PriorityStrategy.ts +0 -46
  252. package/packages/core/src/storage/HiveDBStorage.ts +0 -64
  253. package/packages/core/src/storage/SQLiteStorage.ts +0 -414
  254. package/packages/core/src/storage/hiveSeed.ts +0 -308
  255. package/packages/core/src/storage/hiveStorage.test.ts +0 -38
  256. package/packages/core/src/storage/schema.ts +0 -689
  257. package/packages/core/src/storage/storage.test.ts +0 -37
  258. package/packages/core/src/swarm/AgentBus.ts +0 -460
  259. package/packages/core/src/swarm/EventBus.ts +0 -169
  260. package/packages/core/src/swarm/WorkerPool.ts +0 -236
  261. package/packages/core/src/tools/bridge-events.ts +0 -26
  262. package/packages/core/src/tools/canvas/index.ts +0 -375
  263. package/packages/core/src/tools/codebridge/index.ts +0 -342
  264. package/packages/core/src/tools/meeting/index.ts +0 -353
  265. package/packages/core/src/tools/projects/index.ts +0 -37
  266. package/packages/core/src/tools/projects/project-create.ts +0 -94
  267. package/packages/core/src/tools/projects/project-done.ts +0 -66
  268. package/packages/core/src/tools/projects/project-fail.ts +0 -66
  269. package/packages/core/src/tools/projects/project-list.ts +0 -96
  270. package/packages/core/src/tools/projects/project-update.ts +0 -72
  271. package/packages/core/src/tools/projects/task-create.ts +0 -68
  272. package/packages/core/src/tools/projects/task-evaluate.ts +0 -93
  273. package/packages/core/src/tools/projects/task-update.ts +0 -93
  274. package/packages/core/src/tools/voice/index.ts +0 -104
  275. package/packages/core/src/tools/web/api-request.test.ts +0 -170
  276. package/packages/core/src/tools/web/api-request.ts +0 -239
  277. package/test/setup-db.ts +0 -216
  278. /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();