@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,12 +1,14 @@
1
1
  /**
2
- * Hive Scheduler Module
3
- *
4
- * Croner-based scheduling system for Hive.
5
- * Supports recurring and one-shot cron jobs with SQLite persistence.
2
+ * Scheduler — cron con Croner sobre HiveDB.
3
+ *
4
+ * Soporta jobs recurrentes y de una sola vez. La persistencia pasó de SQLite a
5
+ * las colecciones `cronJobs` / `taskRuns` de HiveDB en 0.1.5.
6
6
  */
7
7
 
8
- export { CronScheduler } from "./CronScheduler";
9
- export { executeScheduledTask, createTaskHandler, notifyTaskCompletion, setSchedulerForCleanup } from "./integration";
8
+ export { CronScheduler } from "./CronScheduler.ts";
9
+ // `executeScheduledTask` dejó de ser público: la ejecución entra por
10
+ // `createTaskHandler()`, que es lo que el scheduler engancha.
11
+ export { createTaskHandler, notifyTaskCompletion, setSchedulerForCleanup } from "./integration.ts";
10
12
  export type {
11
13
  CronJob,
12
14
  TaskRun,
@@ -19,4 +21,4 @@ export type {
19
21
  TaskStatus,
20
22
  TaskRunStatus,
21
23
  CronerOptions,
22
- } from "./types";
24
+ } from "./types.ts";
@@ -7,18 +7,20 @@
7
7
 
8
8
  import type { CronJob, CronJobExecutionResult } from "./types";
9
9
  import { logger } from "../utils/logger";
10
- import { getDb } from "../storage/SQLiteStorage";
11
- import { buildAgentLoop } from "../agent/AgentRunner";
10
+ import { buildAgentLoop } from "../agent/agent-loop";
12
11
  import { resolveAgentId } from "../storage/onboarding";
13
12
  import { sendToUserChannel } from "../gateway/channel-notify";
14
- import { addMessage } from "../agent/ConversationStore";
13
+ import { getNarration } from "../events/tool-narration.ts";
14
+ import { addMessage } from "../agent/conversation-store";
15
15
  import { resolveBestChannel } from "../tools/cron/index";
16
+ import { col } from "../storage/hive";
17
+ import type { UserDoc, CronJobDoc } from "../storage/collections";
16
18
 
17
19
  const log = logger.child("SchedulerIntegration");
18
20
 
19
- let _scheduler: { runCleanup(): void } | null = null;
21
+ let _scheduler: { runCleanup(): Promise<void> } | null = null;
20
22
 
21
- export function setSchedulerForCleanup(scheduler: { runCleanup(): void }): void {
23
+ export function setSchedulerForCleanup(scheduler: { runCleanup(): Promise<void> }): void {
22
24
  _scheduler = scheduler;
23
25
  }
24
26
 
@@ -32,7 +34,7 @@ export function setSchedulerForCleanup(scheduler: { runCleanup(): void }): void
32
34
  * 4. Executes the tool if tool_name is specified
33
35
  * 5. Returns the agent response
34
36
  */
35
- export async function executeScheduledTask(job: CronJob): Promise<CronJobExecutionResult> {
37
+ async function executeScheduledTask(job: CronJob): Promise<CronJobExecutionResult> {
36
38
  log.info(`[execute] Processing job "${job.name}" (${job.id})`);
37
39
 
38
40
  try {
@@ -52,7 +54,7 @@ export async function executeScheduledTask(job: CronJob): Promise<CronJobExecuti
52
54
 
53
55
  if (payload._internal === true && (payload as any).action === "cleanup") {
54
56
  if (_scheduler) {
55
- _scheduler.runCleanup();
57
+ await _scheduler.runCleanup();
56
58
  } else {
57
59
  log.warn("[execute] Cleanup job fired but scheduler instance not available");
58
60
  }
@@ -71,18 +73,19 @@ export async function executeScheduledTask(job: CronJob): Promise<CronJobExecuti
71
73
  };
72
74
 
73
75
  let targetAgentId: string | null = job.agent_id || null;
74
-
76
+
75
77
  if (!targetAgentId) {
76
- targetAgentId = resolveAgentId(null);
78
+ targetAgentId = await resolveAgentId(null);
77
79
  log.debug(`[execute] No agent specified, routing to Coordinator: ${targetAgentId}`);
78
80
  }
79
81
 
80
- const db = getDb();
81
- const user = db.query("SELECT id, timezone, language FROM users LIMIT 1").get() as {
82
- id: string;
83
- timezone: string;
84
- language: string | null;
85
- } | undefined;
82
+ const usersCol = await col<UserDoc>("users");
83
+ const userEntry = (await usersCol.scan({ limit: 1 }))[0];
84
+ const user = userEntry ? {
85
+ id: userEntry.doc.id,
86
+ timezone: userEntry.doc.timezone || "UTC",
87
+ language: userEntry.doc.language,
88
+ } : undefined;
86
89
 
87
90
  const userTimezone = user?.timezone || "UTC";
88
91
  const userLanguage = user?.language || "en";
@@ -124,7 +127,24 @@ ${prompt || `Execute tool: ${job.tool_name}`}`;
124
127
 
125
128
  const agentChannel = (job.channel && job.channel !== "system")
126
129
  ? job.channel
127
- : resolveBestChannel(user?.id || "");
130
+ : await resolveBestChannel(user?.id || "");
131
+
132
+ // Narrate progress to the user's channel as the task runs, same as a
133
+ // live chat turn (server.ts onStep) — without this, scheduled tasks
134
+ // execute silently and only the final completion message is sent.
135
+ const onStep = async (step: { type: string; message?: string; toolName?: string }) => {
136
+ if (!agentChannel) return;
137
+ try {
138
+ if (step.type === "tool_call" && step.toolName) {
139
+ await sendToUserChannel(agentChannel, user?.id || "", getNarration(step.toolName));
140
+ } else if (step.type === "text" && step.message) {
141
+ const trimmed = step.message.trim();
142
+ if (trimmed) await sendToUserChannel(agentChannel, user?.id || "", trimmed);
143
+ }
144
+ } catch (err) {
145
+ log.warn(`[execute] Narration send failed: ${(err as Error).message}`);
146
+ }
147
+ };
128
148
 
129
149
  const messages = [{ role: "user", content: contextPrompt }];
130
150
  const stream = agentLoop.stream({ messages }, {
@@ -136,6 +156,7 @@ ${prompt || `Execute tool: ${job.tool_name}`}`;
136
156
  system_prompt: undefined,
137
157
  raw_user_message: contextPrompt,
138
158
  },
159
+ onStep,
139
160
  });
140
161
 
141
162
  let response = "";
@@ -194,22 +215,21 @@ export async function notifyTaskCompletion(
194
215
  response?: string,
195
216
  error?: string
196
217
  ): Promise<void> {
197
- const db = getDb();
198
-
199
- const task = db.query(
200
- "SELECT channel, agent_id FROM cron_jobs WHERE id = ?"
201
- ).get(taskId) as { channel: string; agent_id: string | null } | undefined;
218
+ const cronJobsCol = await col<CronJobDoc>("cronJobs");
219
+ const taskEntry = await cronJobsCol.get(taskId);
202
220
 
203
- if (!task) {
221
+ if (!taskEntry) {
204
222
  log.warn(`[notify] Job "${taskId}" not found`);
205
223
  return;
206
224
  }
225
+ const task = taskEntry.doc;
207
226
 
208
- const userRow = db.query("SELECT id FROM users LIMIT 1").get() as { id: string } | undefined;
209
- const userId = userRow?.id || "";
227
+ const usersCol = await col<UserDoc>("users");
228
+ const userEntry = (await usersCol.scan({ limit: 1 }))[0];
229
+ const userId = userEntry?.id || "";
210
230
 
211
231
  const explicitChannel = task.channel && task.channel !== "system" ? task.channel : undefined;
212
- const notifyChannel = resolveBestChannel(userId, explicitChannel) || "webchat";
232
+ const notifyChannel = (await resolveBestChannel(userId, explicitChannel)) || "webchat";
213
233
  log.info(`[notifyTaskCompletion] task.channel=${task.channel} explicit=${explicitChannel} resolved=${notifyChannel}`);
214
234
 
215
235
  const status = success ? "✅" : "❌";
@@ -220,7 +240,7 @@ export async function notifyTaskCompletion(
220
240
  log.info(`[notify] Sending notification to ${notifyChannel}: "${message.slice(0, 50)}..."`);
221
241
 
222
242
  try {
223
- addMessage(userId, "assistant", message, { channel: notifyChannel });
243
+ await addMessage(userId, "assistant", message, { channel: notifyChannel });
224
244
  } catch (e) {
225
245
  log.warn(`[notify] Failed to persist notification to DB: ${(e as Error).message}`);
226
246
  }
@@ -1,19 +1,15 @@
1
- import { describe, expect, it, beforeAll, afterAll } from "bun:test";
2
- import { CronScheduler } from "./CronScheduler.ts";
3
- import { getTestDb, setupTestDb, teardownTestDb } from "../../../../test/setup-db.ts";
1
+ process.env.HIVE_DB_PATH = ":memory:";
4
2
 
5
- describe("CronScheduler", () => {
6
- beforeAll(() => {
7
- setupTestDb();
8
- });
3
+ import { describe, expect, it } from "bun:test";
4
+ import { CronScheduler } from "./CronScheduler.ts";
9
5
 
10
- afterAll(() => {
11
- teardownTestDb();
12
- });
6
+ // `new CronScheduler(db, handler)` pasó a `new CronScheduler(handler)`: los jobs
7
+ // viven en la colección `cronJobs` de HiveDB y el scheduler la abre solo, así
8
+ // que ya no recibe un handle de base.
13
9
 
14
- it("creates a scheduler instance with db and handler", () => {
15
- const db = getTestDb();
16
- const scheduler = new CronScheduler(db, async () => ({ success: true }));
10
+ describe("CronScheduler", () => {
11
+ it("creates a scheduler instance with a handler", () => {
12
+ const scheduler = new CronScheduler(async () => ({ success: true }));
17
13
  expect(scheduler).toBeDefined();
18
14
  });
19
15
  });
@@ -5,7 +5,6 @@
5
5
  * All names use "CronJob" terminology (formerly ScheduledTask).
6
6
  */
7
7
 
8
- import type { Database } from "bun:sqlite";
9
8
  import type { Cron } from "croner";
10
9
 
11
10
  /**
@@ -24,7 +23,7 @@ export type TaskStatus = "active" | "paused" | "completed" | "failed" | "cancell
24
23
  export type TaskRunStatus = "running" | "success" | "failed" | "timeout";
25
24
 
26
25
  /**
27
- * CronJob as stored in SQLite (cron_jobs table)
26
+ * CronJob as stored in the `cronJobs` HiveDB collection
28
27
  */
29
28
  export interface CronJob {
30
29
  id: string;
@@ -48,6 +47,8 @@ export interface CronJob {
48
47
  run_count: number;
49
48
  error_count: number;
50
49
  last_error: string | null;
50
+ misfire_policy?: "skip" | "fire_once";
51
+ misfire_grace_min?: number;
51
52
  created_at: string;
52
53
  updated_at: string;
53
54
  last_run_at: string | null;
@@ -90,6 +91,8 @@ export interface CreateCronJobInput {
90
91
  max_runs?: number | null;
91
92
  protect?: boolean;
92
93
  interval_sec?: number | null;
94
+ misfire_policy?: "skip" | "fire_once";
95
+ misfire_grace_min?: number;
93
96
  }
94
97
 
95
98
  /**
@@ -113,6 +116,8 @@ export interface UpdateCronJobInput {
113
116
  protect?: boolean;
114
117
  interval_sec?: number | null;
115
118
  status?: TaskStatus;
119
+ misfire_policy?: "skip" | "fire_once";
120
+ misfire_grace_min?: number;
116
121
  }
117
122
 
118
123
  /**
@@ -1,5 +1,5 @@
1
1
  import crypto from "crypto";
2
- import { eventBus } from "../swarm/EventBus.ts";
2
+ import { eventBus } from "../events/event-bus.ts";
3
3
  import { logger } from "../utils/logger.ts";
4
4
 
5
5
  export interface PairingCode {
@@ -0,0 +1,176 @@
1
+ ---
2
+ name: a2ui_dashboard
3
+ description: "Create real-time interactive dashboards using A2UI v0.9 protocol with dynamic data binding and live updates"
4
+ version: 1.0.0
5
+ author: Hive Team
6
+ icon: "📊"
7
+ category: a2ui
8
+ permissions:
9
+ - a2ui_write
10
+ dependencies: []
11
+ tools: [a2ui_create_surface, a2ui_update_components, a2ui_update_data_model, a2ui_delete_surface]
12
+
13
+ # Structured skill fields
14
+ triggers:
15
+ - "dashboard A2UI"
16
+ - "panel de control A2UI"
17
+ - "A2UI dashboard"
18
+ - "mostrar métricas A2UI"
19
+ - "A2UI metrics"
20
+ - "dashboard interactivo A2UI"
21
+ - "interactive dashboard"
22
+ - "A2UI dashboard en tiempo real"
23
+ - "real-time dashboard A2UI"
24
+ - "mostrar datos A2UI"
25
+ - "visualizar datos con A2UI"
26
+
27
+ preferred_agents: []
28
+
29
+ steps:
30
+ - step: 1
31
+ action: a2ui_create_surface
32
+ instruction: "Create an A2UI surface for the dashboard. Set theme with primaryColor matching project branding."
33
+ params:
34
+ surfaceId: "Descriptive ID (e.g. 'project_dashboard', 'metrics_dashboard')"
35
+ catalogId: "https://a2ui.org/specification/v0_9/basic_catalog.json"
36
+ theme: "{ primaryColor: '#10B981', agentDisplayName: 'Dashboard' }"
37
+ output: surface_created
38
+
39
+ - step: 2
40
+ action: a2ui_update_components
41
+ instruction: "Build dashboard layout using Row, Column, Card, and Text components. Use weight for proportional sizing."
42
+ params:
43
+ surfaceId: "Same surfaceId from step 1"
44
+ components: "Array of A2UI component definitions for dashboard layout"
45
+ output: dashboard_layout
46
+
47
+ - step: 3
48
+ action: a2ui_update_data_model
49
+ instruction: "Populate dashboard with initial data using JSON Pointer paths. All dynamic values should use path bindings."
50
+ params:
51
+ surfaceId: "Same surfaceId"
52
+ path: "/"
53
+ value: "Full data model object with all metric values"
54
+ output: data_populated
55
+
56
+ - step: 4
57
+ action: a2ui_update_data_model
58
+ instruction: "Update specific data model paths to refresh dashboard metrics in real-time."
59
+ params:
60
+ surfaceId: "Same surfaceId"
61
+ path: "/metrics/completionRate"
62
+ value: "Updated value"
63
+ output: metrics_updated
64
+
65
+ - step: 5
66
+ action: a2ui_delete_surface
67
+ instruction: "Delete the dashboard surface when no longer needed."
68
+ params:
69
+ surfaceId: "Same surfaceId"
70
+ output: surface_deleted
71
+
72
+ rules:
73
+ - "Always call a2ui_create_surface BEFORE a2ui_update_components"
74
+ - "Components MUST include a root component with id='root'"
75
+ - "Use Row with weight for side-by-side metrics (e.g. weight: 1 vs weight: 3)"
76
+ - "Use Card to group related metrics together"
77
+ - "Use Text with usageHint: 'h2' for section titles, 'h1' for dashboard title"
78
+ - "Use Text with usageHint: 'caption' for labels"
79
+ - "Use data binding { path: '/metrics/name' } for all dynamic values"
80
+ - "Update metrics with a2ui_update_data_model using specific JSON Pointer paths"
81
+ - "Use a2ui_update_data_model with path: '/' to replace entire data model if needed"
82
+ - "Delete surfaces with a2ui_delete_surface when dashboard is no longer needed"
83
+ - "Use A2UI for both static and live dashboards"
84
+
85
+ output_format:
86
+ structure: a2ui_dashboard
87
+ sections:
88
+ - "surface_creation"
89
+ - "dashboard_layout"
90
+ - "data_model"
91
+ - "live_updates"
92
+ - "cleanup"
93
+ max_length: "Structured JSON components"
94
+
95
+ examples:
96
+ - user_input: "mostrá el dashboard del proyecto"
97
+ expected_behavior: "a2ui_create_surface(surfaceId:'project_dash', theme:{primaryColor:'#10B981'}) → a2ui_update_components with Row[Column[Card[metrics]],Column[tasks]] → a2ui_update_data_model(path:'/', value:{metrics:{...}})"
98
+
99
+ - user_input: "actualizá las métricas del dashboard"
100
+ expected_behavior: "a2ui_update_data_model(surfaceId:'project_dash', path:'/metrics/completionRate', value:75)"
101
+
102
+ - user_input: "show a real-time dashboard with server metrics"
103
+ expected_behavior: "a2ui_create_surface → a2ui_update_components with Cards for each metric → a2ui_update_data_model → periodic updates with a2ui_update_data_model"
104
+ ---
105
+
106
+ # A2UI Dashboard Skill
107
+
108
+ ## Cuándo se Activa
109
+
110
+ Para crear dashboards interactivos en tiempo real usando A2UI v0.9. Usar cuando se necesita:
111
+ - Métricas que se actualizan dinámicamente
112
+ - Dashboards con data binding
113
+ - Paneles con Cards, Rows, Columns
114
+ - Visualización de datos que cambia en tiempo real
115
+
116
+ ## Herramientas Disponibles
117
+
118
+ | Tool | Qué hace | Cuándo usarla |
119
+ |------|----------|---------------|
120
+ | `a2ui_create_surface` | Crea la superficie A2UI | Siempre primero |
121
+ | `a2ui_update_components` | Envía componentes | Para layout del dashboard |
122
+ | `a2ui_update_data_model` | Actualiza datos | Para métricas dinámicas |
123
+ | `a2ui_delete_surface` | Elimina la superficie | Al cerrar dashboard |
124
+
125
+ ## Flujo Obligatorio
126
+
127
+ ```
128
+ 1. a2ui_create_surface(surfaceId, catalogId, theme)
129
+ 2. a2ui_update_components(surfaceId, components[])
130
+ 3. a2ui_update_data_model(surfaceId, path, value) // datos iniciales
131
+ 4. [actualizar métricas con a2ui_update_data_model según necesidad]
132
+ 5. a2ui_delete_surface(surfaceId) // al terminar
133
+ ```
134
+
135
+ ## Patrón de Dashboard Típico
136
+
137
+ ```json
138
+ [
139
+ {"id": "root", "component": "Column", "children": ["title", "metrics_row", "tasks_list"]},
140
+ {"id": "title", "component": "Text", "text": "Dashboard de Proyecto", "variant": "h1"},
141
+
142
+ {"id": "metrics_row", "component": "Row", "children": ["card1", "card2", "card3"]},
143
+ {"id": "card1", "component": "Card", "child": "card1_content", "weight": 1},
144
+ {"id": "card1_content", "component": "Column", "children": ["card1_label", "card1_value"]},
145
+ {"id": "card1_label", "component": "Text", "text": "Completado", "variant": "caption"},
146
+ {"id": "card1_value", "component": "Text", "text": {"path": "/metrics/completionRate"}, "variant": "h2"},
147
+
148
+ {"id": "tasks_list", "component": "List", "children": {"path": "/tasks", "componentId": "task_template"}},
149
+ {"id": "task_template", "component": "Card", "child": "task_content"},
150
+ {"id": "task_content", "component": "Column", "children": ["task_name", "task_status"]},
151
+ {"id": "task_name", "component": "Text", "text": {"path": "/name"}},
152
+ {"id": "task_status", "component": "Text", "text": {"path": "/status"}, "variant": "caption"}
153
+ ]
154
+ ```
155
+
156
+ ## Actualización en Tiempo Real
157
+
158
+ Para actualizar métricas específicas sin reenviar componentes:
159
+ ```json
160
+ a2ui_update_data_model(surfaceId: "dash", path: "/metrics/completionRate", value: 85)
161
+ a2ui_update_data_model(surfaceId: "dash", path: "/metrics/totalTasks", value: 24)
162
+ ```
163
+
164
+ Para reemplazar todo el data model:
165
+ ```json
166
+ a2ui_update_data_model(surfaceId: "dash", path: "/", value: {metrics: {completionRate: 90, totalTasks: 25}, tasks: [...]})
167
+ ```
168
+
169
+ ## Mejores Prácticas
170
+
171
+ - Usar `weight` en Row/Column para proporciones (weight:1 vs weight:3 = 25% vs 75%)
172
+ - Agrupar métricas en Cards para separación visual
173
+ - Usar `usageHint: "caption"` para labels, `"h1"/"h2"` para valores
174
+ - Bind todos los valores dinámicos con `{ path: "/..." }`
175
+ - Actualizar métricas con `a2ui_update_data_model` path específico
176
+ - Eliminar surfaces al terminar para evitar memory leaks
@@ -0,0 +1,202 @@
1
+ ---
2
+ name: a2ui_form
3
+ description: "Create rich interactive forms using A2UI v0.9 protocol with validation, data binding, and multi-step flows"
4
+ version: 1.0.0
5
+ author: Hive Team
6
+ icon: "📝"
7
+ category: a2ui
8
+ permissions:
9
+ - a2ui_write
10
+ dependencies: []
11
+ tools: [a2ui_create_surface, a2ui_update_components, a2ui_update_data_model, a2ui_delete_surface]
12
+
13
+ # Structured skill fields
14
+ triggers:
15
+ - "crear formulario A2UI"
16
+ - "create A2UI form"
17
+ - "formulario interactivo A2UI"
18
+ - "A2UI form"
19
+ - "pedir datos con A2UI"
20
+ - "collect data A2UI"
21
+ - "formulario con validación"
22
+ - "form with validation"
23
+ - "formulario multi-paso"
24
+ - "multi-step form A2UI"
25
+ - "form dinámico A2UI"
26
+ - "dynamic form A2UI"
27
+
28
+ preferred_agents: []
29
+
30
+ steps:
31
+ - step: 1
32
+ action: a2ui_create_surface
33
+ instruction: "Create an A2UI surface with a unique surfaceId and catalog. Set theme with primaryColor and agentDisplayName."
34
+ params:
35
+ surfaceId: "Unique identifier (e.g. 'contact_form', 'signup_form')"
36
+ catalogId: "https://a2ui.org/specification/v0_9/basic_catalog.json"
37
+ theme: "{ primaryColor: '#3B82F6', agentDisplayName: 'Asistente' }"
38
+ output: surface_created
39
+
40
+ - step: 2
41
+ action: a2ui_update_components
42
+ instruction: "Send the form layout as a flat list of A2UI components. Must include a 'root' component. Use Column/Row for layout, TextField for inputs, Button for submit."
43
+ params:
44
+ surfaceId: "Same surfaceId from step 1"
45
+ components: "Array of A2UI component definitions"
46
+ output: components_sent
47
+
48
+ - step: 3
49
+ action: a2ui_update_data_model
50
+ instruction: "Populate initial form values using JSON Pointer paths (e.g. '/form/name'). Sets default values and enables two-way binding."
51
+ params:
52
+ surfaceId: "Same surfaceId"
53
+ path: "/form"
54
+ value: "{ name: '', email: '' }"
55
+ output: data_populated
56
+
57
+ - step: 4
58
+ action: a2ui_delete_surface
59
+ instruction: "Delete the A2UI surface when the form is completed or no longer needed."
60
+ params:
61
+ surfaceId: "Same surfaceId"
62
+ output: surface_deleted
63
+
64
+ rules:
65
+ - "Always call a2ui_create_surface BEFORE a2ui_update_components"
66
+ - "Components MUST include a root component with id='root'"
67
+ - "Use data binding with { path: '/form/field' } for TextField values to enable two-way binding"
68
+ - "Add checks (validation) to TextField: [{ call: 'required', args: { value: { path: '/form/email' } }, message: 'Email is required' }]"
69
+ - "Button actions use event format: { event: { name: 'submit_form', context: { email: { path: '/form/email' } } } }"
70
+ - "Use Column for vertical layouts, Row for horizontal layouts"
71
+ - "Set weight on Row/Column children for proportional sizing (e.g. weight: 1 vs weight: 3)"
72
+ - "Delete surfaces with a2ui_delete_surface when no longer needed to prevent memory leaks"
73
+ - "Use A2UI forms for every structured data-entry flow"
74
+
75
+ output_format:
76
+ structure: a2ui_form
77
+ sections:
78
+ - "surface_creation"
79
+ - "component_layout"
80
+ - "data_model"
81
+ - "cleanup"
82
+ max_length: "Structured JSON components"
83
+
84
+ examples:
85
+ - user_input: "creá un formulario de contacto"
86
+ expected_behavior: "a2ui_create_surface(surfaceId:'contact_form', theme:{primaryColor:'#3B82F6',agentDisplayName:'Asistente'}) → a2ui_update_components(surfaceId:'contact_form', components:[root,Column,hdr,name_fld,email_fld,msg_fld,submit_btn]) → a2ui_update_data_model(surfaceId:'contact_form', path:'/form', value:{name:'',email:'',message:''})"
87
+
88
+ - user_input: "I need a signup form with email validation"
89
+ expected_behavior: "a2ui_create_surface → a2ui_update_components with TextField({value:{path:'/form/email'}, checks:[{call:'required',...},{call:'email',...}]}) → a2ui_update_data_model"
90
+
91
+ - user_input: "creá un formulario multi-paso"
92
+ expected_behavior: "a2ui_create_surface → a2ui_update_components with Tabs+Column per step → a2ui_update_data_model for all fields"
93
+ ---
94
+
95
+ # A2UI Form Skill
96
+
97
+ ## Cuándo se Activa
98
+
99
+ Para crear formularios interactivos ricos usando el protocolo A2UI v0.9. Usar cuando se necesita:
100
+ - Validación de campos (required, email, regex)
101
+ - Data binding dinámico
102
+ - Formularios multi-paso
103
+ - Choice pickers, sliders, checkboxes
104
+ - Formularios con acciones personalizadas
105
+
106
+ ## Herramientas Disponibles
107
+
108
+ | Tool | Qué hace | Cuándo usarla |
109
+ |------|----------|---------------|
110
+ | `a2ui_create_surface` | Crea la superficie A2UI | Siempre primero |
111
+ | `a2ui_update_components` | Envía componentes | Después de crear surface |
112
+ | `a2ui_update_data_model` | Actualiza datos | Para valores iniciales o dinámicos |
113
+ | `a2ui_delete_surface` | Elimina la superficie | Al terminar |
114
+
115
+ ## Flujo Obligatorio
116
+
117
+ ```
118
+ 1. a2ui_create_surface(surfaceId, catalogId, theme)
119
+ 2. a2ui_update_components(surfaceId, components[])
120
+ 3. a2ui_update_data_model(surfaceId, path, value) // opcional, para datos iniciales
121
+ 4. [esperar acción del usuario]
122
+ 5. a2ui_delete_surface(surfaceId) // al terminar
123
+ ```
124
+
125
+ ## Componentes Disponibles
126
+
127
+ | Componente | Descripción | Props clave |
128
+ |------------|-------------|-------------|
129
+ | `Column` | Layout vertical | `children`, `distribution`, `alignment` |
130
+ | `Row` | Layout horizontal | `children`, `distribution`, `alignment` |
131
+ | `Text` | Texto | `text`, `usageHint` (h1-h5, body, caption, code) |
132
+ | `Button` | Botón | `child`, `variant`, `action` |
133
+ | `TextField` | Campo de texto | `label`, `value`, `variant` (shortText/longText/number/obscured), `validationRegexp`, `checks`, `action` |
134
+ | `CheckBox` | Checkbox | `label`, `value` |
135
+ | `ChoicePicker` | Selector múltiple | `options`, `value` (DynamicStringList), `variant` (mutuallyExclusive/multipleSelection), `displayStyle`, `filterable`, `action` |
136
+ | `Slider` | Slider numérico | `value`, `minValue`, `maxValue` |
137
+ | `DateTimeInput` | Fecha/hora | `value`, `enableDate`, `enableTime` |
138
+ | `Card` | Tarjeta | `child` |
139
+ | `Divider` | Separador | `axis` |
140
+ | `Image` | Imagen | `url`, `fit` |
141
+ | `Tabs` | Pestañas | `tabItems` |
142
+
143
+ ## Data Binding
144
+
145
+ - Literal: `"texto directo"` o número
146
+ - Path: `{ "path": "/form/name" }` — se resuelve contra el data model
147
+ - Function call: `{ "call": "formatDate", "args": {...} }`
148
+
149
+ ## Cuándo disparan acciones los inputs
150
+
151
+ | Componente | Cuándo dispara | Formato de action |
152
+ |------------|---------------|-------------------|
153
+ | `Button` | Al hacer click | `{name: "...", context: {...}}` o `{event: {name: "...", context: {...}}}` |
154
+ | `TextField` | Al perder foco (blur) o presionar Enter (en shortText) | `{name: "...", context: {...}}` |
155
+ | `ChoicePicker` | Inmediatamente al seleccionar/deseleccionar | `{name: "...", context: {...}}` |
156
+ | `Slider` | Al soltar el slider (onValueCommit) | `{name: "...", context: {...}}` |
157
+ | `CheckBox` | Al cambiar estado | — (solo two-way binding) |
158
+ | `DateTimeInput` | Al cambiar valor | — (solo two-way binding) |
159
+
160
+ **Nota**: Tanto `{name: "...", context: {...}}` (directo) como `{event: {name: "...", context: {...}}}` (con wrapper) son formatos válidos.
161
+
162
+ **Nota**: Para ChoicePicker usa siempre `selections: {path: "..."}` (no `value`) para two-way binding.
163
+
164
+ ## Validación (checks)
165
+
166
+ ```json
167
+ "checks": [
168
+ { "call": "required", "args": { "value": { "path": "/form/email" } }, "message": "Email is required" },
169
+ { "call": "email", "args": { "value": { "path": "/form/email" } }, "message": "Invalid email" },
170
+ { "call": "regex", "args": { "value": { "path": "/form/phone" }, "pattern": "^\\d{10}$" }, "message": "10 digits required" }
171
+ ]
172
+ ```
173
+
174
+ ## Ejemplo: Formulario de Contacto
175
+
176
+ ```json
177
+ // 1. Create surface
178
+ a2ui_create_surface(surfaceId: "contact_form", catalogId: "https://a2ui.org/specification/v0_9/basic_catalog.json", theme: {primaryColor: "#3B82F6", agentDisplayName: "Asistente"})
179
+
180
+ // 2. Send components
181
+ a2ui_update_components(surfaceId: "contact_form", components: [
182
+ {"id": "root", "component": "Column", "children": ["header","name_field","email_field","msg_field","submit_btn"]},
183
+ {id: "header", component: "Text", text: "Contacto", variant: "h2"},
184
+ {id: "name_field", component: "TextField", label: "Nombre", value: {path: "/form/name"}, variant: "shortText"},
185
+ {id: "email_field", component: "TextField", label: "Email", value: {path: "/form/email"}, variant: "shortText", validationRegexp: "^[^@]+@[^@]+\\.[^@]+$", checks: [{call: "required", args: {value: {path: "/form/email"}}, message: "Email obligatorio"}, {call: "email", args: {value: {path: "/form/email"}}, message: "Email inválido"}]},
186
+ {id: "msg_field", component: "TextField", label: "Mensaje", value: {path: "/form/message"}, variant: "longText"},
187
+ {id: "submit_label", component: "Text", text: "Enviar"},
188
+ {id: "submit_btn", component: "Button", child: "submit_label", variant: "primary", action: {event: {name: "submit_contact", context: {name: {path: "/form/name"}, email: {path: "/form/email"}, message: {path: "/form/message"}}}}
189
+ ])
190
+
191
+ // 3. Initialize data model
192
+ a2ui_update_data_model(surfaceId: "contact_form", path: "/form", value: {name: "", email: "", message: ""})
193
+ ```
194
+
195
+ ## Mejores Prácticas
196
+
197
+ - Siempre incluir un componente `root` con id="root"
198
+ - Usar `{ path: "/..." }` para data binding en TextField values
199
+ - Agregar `checks` para validación de campos obligatorios
200
+ - Usar `variant: "primary"` para botones principales
201
+ - Eliminar surfaces con `a2ui_delete_surface` al terminar
202
+ - Usar formularios A2UI para toda captura estructurada de datos