@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
@@ -9,8 +9,9 @@
9
9
  */
10
10
 
11
11
  import type { Tool } from "../types";
12
- import { getDb } from "../../storage/SQLiteStorage.ts";
13
- import { logger } from "../../utils/logger.ts";
12
+ import { col, toIndexable } from "../../storage/hive";
13
+ import type { UserDoc, UserIdentityDoc, ChannelDoc, CronJobDoc, TaskRunDoc } from "../../storage/collections";
14
+ import { logger } from "../../utils/logger";
14
15
  import { Cron } from "croner";
15
16
 
16
17
  const log = logger.child("CronTools");
@@ -25,59 +26,71 @@ export function getSchedulerInstance(): any {
25
26
  return _scheduler;
26
27
  }
27
28
 
28
- function getUserTimezone(): string {
29
- const db = getDb();
30
- const user = db.query("SELECT timezone FROM users LIMIT 1").get() as { timezone: string } | undefined;
31
- return user?.timezone || "UTC";
29
+ async function getUserTimezone(): Promise<string> {
30
+ const usersCol = await col<UserDoc>("users");
31
+ const userEntry = (await usersCol.scan({ limit: 1 }))[0];
32
+ return userEntry?.doc.timezone || "UTC";
32
33
  }
33
34
 
34
- export function resolveBestChannel(userId: string, explicitChannel?: string): string {
35
- const db = getDb();
36
-
37
- const user = db.query("SELECT preferred_cron_channel FROM users WHERE id = ? LIMIT 1").get(userId) as {
38
- preferred_cron_channel: string;
39
- } | undefined;
35
+ export async function resolveBestChannel(userId: string, explicitChannel?: string): Promise<string> {
36
+ const usersCol = await col<UserDoc>("users");
37
+ const userEntry = await usersCol.get(userId);
38
+ const preferredCronChannel = userEntry?.doc.preferred_cron_channel;
39
+
40
+ const identitiesCol = await col<UserIdentityDoc>("userIdentities");
41
+ const identityEntries = await identitiesCol.scan({ prefix: `${userId}:` });
42
+ const allIdentityChannels = identityEntries.map(e => e.doc.channel);
43
+
44
+ const channelsCol = await col<ChannelDoc>("channels");
45
+ const activeChannels: string[] = [];
46
+ for (const channel of new Set(allIdentityChannels)) {
47
+ const channelEntry = await channelsCol.get(channel);
48
+ if (channelEntry?.doc.active && channelEntry.doc.status === "connected") {
49
+ activeChannels.push(channel);
50
+ }
51
+ }
40
52
 
41
- const activeChannels = db.query(`
42
- SELECT ui.channel FROM user_identities ui
43
- JOIN channels c ON c.id = ui.channel
44
- WHERE ui.user_id = ? AND c.active = 1 AND c.status = 'connected'
45
- `).all(userId) as { channel: string }[];
53
+ log.debug(`[resolveBestChannel] userId=${userId}, explicit=${explicitChannel}, preferred=${preferredCronChannel}, activeChannels=[${activeChannels.join(", ")}]`);
46
54
 
47
- const identities = activeChannels.length > 0
48
- ? activeChannels
49
- : db.query("SELECT channel FROM user_identities WHERE user_id = ?").all(userId) as { channel: string }[];
55
+ const identities = activeChannels.length > 0 ? activeChannels : allIdentityChannels;
50
56
 
51
57
  if (identities.length === 0) {
58
+ log.warn(`[resolveBestChannel] No identities found for user ${userId}, falling back to webchat`);
52
59
  return "webchat";
53
60
  }
54
61
 
55
62
  let bestChannel = "";
56
63
 
57
64
  if (explicitChannel && explicitChannel !== "system") {
58
- if (identities.some((i) => i.channel === explicitChannel)) {
65
+ if (identities.includes(explicitChannel)) {
59
66
  bestChannel = explicitChannel;
67
+ log.info(`[resolveBestChannel] Using explicit channel: ${bestChannel}`);
60
68
  }
61
69
  }
62
70
 
63
- if (!bestChannel && user?.preferred_cron_channel && user.preferred_cron_channel !== "auto") {
64
- if (identities.some((i) => i.channel === user.preferred_cron_channel)) {
65
- bestChannel = user.preferred_cron_channel;
71
+ if (!bestChannel && preferredCronChannel && preferredCronChannel !== "auto") {
72
+ if (identities.includes(preferredCronChannel)) {
73
+ bestChannel = preferredCronChannel;
74
+ log.info(`[resolveBestChannel] Using preferred_cron_channel: ${bestChannel}`);
75
+ } else {
76
+ log.warn(`[resolveBestChannel] preferred_cron_channel=${preferredCronChannel} not in identities=[${identities.join(", ")}]`);
66
77
  }
67
78
  }
68
79
 
69
80
  if (!bestChannel) {
70
81
  const preferred = ["telegram", "discord", "slack", "whatsapp", "webchat"];
71
82
  for (const p of preferred) {
72
- if (identities.some((i) => i.channel === p)) {
83
+ if (identities.includes(p)) {
73
84
  bestChannel = p;
85
+ log.info(`[resolveBestChannel] Using fallback priority: ${bestChannel}`);
74
86
  break;
75
87
  }
76
88
  }
77
89
  }
78
90
 
79
91
  if (!bestChannel) {
80
- bestChannel = identities[0].channel;
92
+ bestChannel = identities[0];
93
+ log.info(`[resolveBestChannel] Using first identity: ${bestChannel}`);
81
94
  }
82
95
 
83
96
  return bestChannel;
@@ -87,7 +100,7 @@ export function resolveBestChannel(userId: string, explicitChannel?: string): st
87
100
 
88
101
  export const cronCreateTool: Tool = {
89
102
  name: "cron.create",
90
- description: "Create a new cron job. Use for recurring reminders, daily reports, automated checks. Spanish: crear tarea programada, agendar recordatorio, programar reporte",
103
+ description: "Create a Hive scheduled automation: a recurring cron job or one-shot future execution. Spanish: crear automatización programada, programar tarea recurrente, ejecutar después, programar reporte",
91
104
  parameters: {
92
105
  type: "object",
93
106
  properties: {
@@ -108,7 +121,7 @@ export const cronCreateTool: Tool = {
108
121
  required: ["name", "task", "task_type"],
109
122
  },
110
123
  execute: async (params: Record<string, unknown>) => {
111
- const timezone = getUserTimezone();
124
+ const timezone = await getUserTimezone();
112
125
 
113
126
  const name = params.name as string | undefined;
114
127
  const task = params.task as string | undefined;
@@ -165,7 +178,7 @@ export const cronCreateTool: Tool = {
165
178
 
166
179
  try {
167
180
  if (_scheduler) {
168
- const result = _scheduler.create({
181
+ const result = await _scheduler.create({
169
182
  name,
170
183
  task,
171
184
  task_type,
@@ -191,24 +204,36 @@ export const cronCreateTool: Tool = {
191
204
  message: `Job "${name}" scheduled. Next run: ${result.nextRun ? new Date(result.nextRun).toLocaleString() : "unknown"}`,
192
205
  };
193
206
  } else {
194
- const db = getDb();
207
+ const cronJobsCol = await col<CronJobDoc>("cronJobs");
195
208
  const id = crypto.randomUUID().replace(/-/g, "").slice(0, 16);
196
209
  const now = new Date().toISOString();
197
210
  const payloadJson = JSON.stringify(payloadObj || { prompt: task });
198
211
 
199
- db.query(`
200
- INSERT INTO cron_jobs (
201
- id, name, task, task_type, cron_expression, fire_at, timezone,
202
- start_at, stop_at, dom_and_dow,
203
- payload, agent_id, tool_name, max_runs, channel,
204
- status, created_at, updated_at
205
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'active', ?, ?)
206
- `).run(
207
- id, name, task, task_type, cron_expression || null, fire_at || null, timezone,
208
- start_at || null, stop_at || null, dom_and_dow ? 1 : 0,
209
- payloadJson, agent_id || null, tool_name || null, max_runs || null, channel,
210
- now, now
211
- );
212
+ await cronJobsCol.put(id, {
213
+ id, name, task, task_type,
214
+ cron_expression: cron_expression || null,
215
+ fire_at: fire_at || null,
216
+ timezone,
217
+ start_at: start_at || null,
218
+ stop_at: stop_at || null,
219
+ dom_and_dow: dom_and_dow ? 1 : 0,
220
+ max_runs: max_runs || null,
221
+ protect: 1,
222
+ interval_sec: null,
223
+ agent_id: toIndexable(agent_id || null),
224
+ channel,
225
+ payload: payloadJson,
226
+ tool_name: tool_name || null,
227
+ status: "active",
228
+ run_count: 0,
229
+ error_count: 0,
230
+ last_error: null,
231
+ created_at: now,
232
+ updated_at: now,
233
+ last_run_at: null,
234
+ next_run_at: null,
235
+ completed_at: null,
236
+ }, { expectedVersion: 0 });
212
237
 
213
238
  return {
214
239
  ok: true,
@@ -236,28 +261,17 @@ export const cronListTool: Tool = {
236
261
  },
237
262
  },
238
263
  execute: async (params: Record<string, unknown>) => {
239
- const db = getDb();
240
-
241
264
  const status = params.status as string | undefined;
242
265
  const task_type = params.task_type as string | undefined;
243
266
 
244
267
  try {
245
- let query = "SELECT * FROM cron_jobs WHERE 1=1";
246
- const args: any[] = [];
247
-
248
- if (status) {
249
- query += " AND status = ?";
250
- args.push(status);
251
- }
268
+ const cronJobsCol = await col<CronJobDoc>("cronJobs");
269
+ let tasks = (await cronJobsCol.scan({})).map(e => e.doc);
252
270
 
253
- if (task_type) {
254
- query += " AND task_type = ?";
255
- args.push(task_type);
256
- }
271
+ if (status) tasks = tasks.filter(t => t.status === status);
272
+ if (task_type) tasks = tasks.filter(t => t.task_type === task_type);
257
273
 
258
- query += " ORDER BY next_run_at ASC";
259
-
260
- const tasks = db.query(query).all(...args) as any[];
274
+ tasks.sort((a, b) => (a.next_run_at ?? "").localeCompare(b.next_run_at ?? ""));
261
275
 
262
276
  return {
263
277
  ok: true,
@@ -336,42 +350,40 @@ export const cronUpdateTool: Tool = {
336
350
 
337
351
  try {
338
352
  if (_scheduler) {
339
- const success = _scheduler.update(task_id, changes);
353
+ const success = await _scheduler.update(task_id, changes);
340
354
  if (success) {
341
355
  return { ok: true, message: `Job "${task_id}" updated` };
342
356
  } else {
343
357
  return { ok: false, error: `Job "${task_id}" not found` };
344
358
  }
345
359
  } else {
346
- const db = getDb();
347
- const fields: string[] = [];
348
- const values: any[] = [];
349
-
350
- if (changes.name !== undefined) { fields.push("name = ?"); values.push(changes.name); }
351
- if (changes.task !== undefined) { fields.push("task = ?"); values.push(changes.task); }
352
- if (changes.cron_expression !== undefined) { fields.push("cron_expression = ?"); values.push(changes.cron_expression); }
353
- if (changes.fire_at !== undefined) { fields.push("fire_at = ?"); values.push(changes.fire_at); }
354
- if (changes.payload !== undefined) { fields.push("payload = ?"); values.push(JSON.stringify(changes.payload)); }
355
- if (changes.channel !== undefined) { fields.push("channel = ?"); values.push(changes.channel); }
356
- if (changes.max_runs !== undefined) { fields.push("max_runs = ?"); values.push(changes.max_runs); }
357
- if (changes.start_at !== undefined) { fields.push("start_at = ?"); values.push(changes.start_at); }
358
- if (changes.stop_at !== undefined) { fields.push("stop_at = ?"); values.push(changes.stop_at); }
359
- if (changes.dom_and_dow !== undefined) { fields.push("dom_and_dow = ?"); values.push(changes.dom_and_dow ? 1 : 0); }
360
- if (changes.agent_id !== undefined) { fields.push("agent_id = ?"); values.push(changes.agent_id); }
361
- if (changes.tool_name !== undefined) { fields.push("tool_name = ?"); values.push(changes.tool_name); }
362
-
363
- if (fields.length === 0) {
360
+ const cronJobsCol = await col<CronJobDoc>("cronJobs");
361
+ const existing = await cronJobsCol.get(task_id);
362
+ if (!existing) {
363
+ return { ok: false, error: `Job "${task_id}" not found` };
364
+ }
365
+
366
+ const patch: Partial<CronJobDoc> = {};
367
+ if (changes.name !== undefined) patch.name = changes.name as string;
368
+ if (changes.task !== undefined) patch.task = changes.task as string;
369
+ if (changes.cron_expression !== undefined) patch.cron_expression = changes.cron_expression as string;
370
+ if (changes.fire_at !== undefined) patch.fire_at = changes.fire_at as string;
371
+ if (changes.payload !== undefined) patch.payload = JSON.stringify(changes.payload);
372
+ if (changes.channel !== undefined) patch.channel = changes.channel as string;
373
+ if (changes.max_runs !== undefined) patch.max_runs = changes.max_runs as number;
374
+ if (changes.start_at !== undefined) patch.start_at = changes.start_at as string;
375
+ if (changes.stop_at !== undefined) patch.stop_at = changes.stop_at as string;
376
+ if (changes.dom_and_dow !== undefined) patch.dom_and_dow = changes.dom_and_dow ? 1 : 0;
377
+ if (changes.agent_id !== undefined) patch.agent_id = toIndexable(changes.agent_id as string);
378
+ if (changes.tool_name !== undefined) patch.tool_name = changes.tool_name as string;
379
+
380
+ if (Object.keys(patch).length === 0) {
364
381
  return { ok: true, message: "No changes to apply" };
365
382
  }
366
383
 
367
- values.push(task_id);
368
- const result = db.query(`UPDATE cron_jobs SET ${fields.join(", ")} WHERE id = ?`).run(...values);
384
+ await cronJobsCol.put(task_id, { ...existing.doc, ...patch }, { expectedVersion: existing.version });
369
385
 
370
- if (result.changes > 0) {
371
- return { ok: true, message: `Job "${task_id}" updated (scheduler not active)` };
372
- } else {
373
- return { ok: false, error: `Job "${task_id}" not found` };
374
- }
386
+ return { ok: true, message: `Job "${task_id}" updated (scheduler not active)` };
375
387
  }
376
388
  } catch (err) {
377
389
  log.error(`[update] Failed: ${(err as Error).message}`);
@@ -401,23 +413,20 @@ export const cronPauseTool: Tool = {
401
413
 
402
414
  try {
403
415
  if (_scheduler) {
404
- const success = _scheduler.pause(task_id);
416
+ const success = await _scheduler.pause(task_id);
405
417
  if (success) {
406
418
  return { ok: true, message: `Job "${task_id}" paused` };
407
419
  } else {
408
420
  return { ok: false, error: `Job "${task_id}" not found or already paused` };
409
421
  }
410
422
  } else {
411
- const db = getDb();
412
- const result = db.query(
413
- "UPDATE cron_jobs SET status = 'paused' WHERE id = ?"
414
- ).run(task_id);
415
-
416
- if (result.changes > 0) {
417
- return { ok: true, message: `Job "${task_id}" paused (scheduler not active)` };
418
- } else {
423
+ const cronJobsCol = await col<CronJobDoc>("cronJobs");
424
+ const existing = await cronJobsCol.get(task_id);
425
+ if (!existing) {
419
426
  return { ok: false, error: `Job "${task_id}" not found` };
420
427
  }
428
+ await cronJobsCol.put(task_id, { ...existing.doc, status: "paused" }, { expectedVersion: existing.version });
429
+ return { ok: true, message: `Job "${task_id}" paused (scheduler not active)` };
421
430
  }
422
431
  } catch (err) {
423
432
  log.error(`[pause] Failed: ${(err as Error).message}`);
@@ -447,23 +456,20 @@ export const cronResumeTool: Tool = {
447
456
 
448
457
  try {
449
458
  if (_scheduler) {
450
- const success = _scheduler.resume(task_id);
459
+ const success = await _scheduler.resume(task_id);
451
460
  if (success) {
452
461
  return { ok: true, message: `Job "${task_id}" resumed` };
453
462
  } else {
454
463
  return { ok: false, error: `Job "${task_id}" not found or already active` };
455
464
  }
456
465
  } else {
457
- const db = getDb();
458
- const result = db.query(
459
- "UPDATE cron_jobs SET status = 'active' WHERE id = ?"
460
- ).run(task_id);
461
-
462
- if (result.changes > 0) {
463
- return { ok: true, message: `Job "${task_id}" resumed (scheduler not active)` };
464
- } else {
466
+ const cronJobsCol = await col<CronJobDoc>("cronJobs");
467
+ const existing = await cronJobsCol.get(task_id);
468
+ if (!existing) {
465
469
  return { ok: false, error: `Job "${task_id}" not found` };
466
470
  }
471
+ await cronJobsCol.put(task_id, { ...existing.doc, status: "active" }, { expectedVersion: existing.version });
472
+ return { ok: true, message: `Job "${task_id}" resumed (scheduler not active)` };
467
473
  }
468
474
  } catch (err) {
469
475
  log.error(`[resume] Failed: ${(err as Error).message}`);
@@ -493,23 +499,20 @@ export const cronDeleteTool: Tool = {
493
499
 
494
500
  try {
495
501
  if (_scheduler) {
496
- const success = _scheduler.delete(task_id);
502
+ const success = await _scheduler.delete(task_id);
497
503
  if (success) {
498
504
  return { ok: true, message: `Job "${task_id}" deleted` };
499
505
  } else {
500
506
  return { ok: false, error: `Job "${task_id}" not found` };
501
507
  }
502
508
  } else {
503
- const db = getDb();
504
- const result = db.query(
505
- "DELETE FROM cron_jobs WHERE id = ?"
506
- ).run(task_id);
507
-
508
- if (result.changes > 0) {
509
- return { ok: true, message: `Job "${task_id}" deleted (scheduler not active)` };
510
- } else {
509
+ const cronJobsCol = await col<CronJobDoc>("cronJobs");
510
+ const existing = await cronJobsCol.get(task_id);
511
+ if (!existing) {
511
512
  return { ok: false, error: `Job "${task_id}" not found` };
512
513
  }
514
+ await cronJobsCol.delete(task_id);
515
+ return { ok: true, message: `Job "${task_id}" deleted (scheduler not active)` };
513
516
  }
514
517
  } catch (err) {
515
518
  log.error(`[delete] Failed: ${(err as Error).message}`);
@@ -577,13 +580,12 @@ export const cronHistoryTool: Tool = {
577
580
  }
578
581
 
579
582
  try {
580
- const db = getDb();
581
- const runs = db.query(`
582
- SELECT * FROM task_runs
583
- WHERE task_id = ?
584
- ORDER BY started_at DESC
585
- LIMIT ?
586
- `).all(task_id, limit) as any[];
583
+ const taskRunsCol = await col<TaskRunDoc>("taskRuns");
584
+ const runs = (await taskRunsCol.scan({}))
585
+ .map(e => e.doc)
586
+ .filter(r => r.task_id === task_id)
587
+ .sort((a, b) => b.started_at.localeCompare(a.started_at))
588
+ .slice(0, limit);
587
589
 
588
590
  return {
589
591
  ok: true,
@@ -619,8 +621,3 @@ export function createTools(): Tool[] {
619
621
  cronHistoryTool,
620
622
  ];
621
623
  }
622
-
623
- /**
624
- * Alias for backward compatibility
625
- */
626
- export const createCronTools = createTools;
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Tools Registry - Exports all 66 tools
3
- *
2
+ * Tools Registry
3
+ *
4
4
  * Import this to get all tools:
5
5
  * import { createAllTools } from "./tools";
6
6
  */
@@ -11,29 +11,20 @@ import type { Config } from "../config/loader.ts";
11
11
  // Filesystem (7)
12
12
  import * as filesystem from "./filesystem/index.ts";
13
13
 
14
- // Web (9)
14
+ // Web (10)
15
15
  import * as web from "./web/index.ts";
16
16
 
17
- // Projects (8)
18
- import * as projects from "./projects/index.ts";
19
-
20
- // Cron (7) - Croner-based scheduler tools
17
+ // Cron (8) - Croner-based scheduler tools
21
18
  import * as cron from "./cron/index.ts";
22
19
 
23
20
  // CLI (1)
24
21
  import * as cli from "./cli/index.ts";
25
22
 
26
- // Agents (14)
23
+ // Agents (15)
27
24
  import * as agents from "./agents/index.ts";
28
25
 
29
- // Canvas (7)
30
- import * as canvas from "./canvas/index.ts";
31
-
32
- // Codebridge (3)
33
- import * as codebridge from "./codebridge/index.ts";
34
-
35
- // Voice (2)
36
- import * as voice from "./voice/index.ts";
26
+ // A2UI (4)
27
+ import * as a2ui from "./a2ui/index.ts";
37
28
 
38
29
  // Core (4)
39
30
  import * as core from "./core/index.ts";
@@ -41,40 +32,56 @@ import * as core from "./core/index.ts";
41
32
  // Office (8)
42
33
  import * as office from "./office/index.ts";
43
34
 
44
- // Meeting (4)
45
- import * as meeting from "./meeting/index.ts";
35
+ // API (1) - HTTP client for REST APIs
36
+ import * as api from "./api/index.ts";
37
+
38
+ // ─── Tools de la aplicación ──────────────────────────────────────────────────
39
+ //
40
+ // Punto de extensión propio del SDK: hive trae todas sus tools compiladas, pero
41
+ // acá `defineTool()` existe justamente para que la app aporte las suyas. Sin
42
+ // esto, una tool de usuario sólo se le podía anunciar al modelo (via
43
+ // `extraTools` del agent loop) pero no tenía ejecutor asociado, así que la
44
+ // llamada moría con "no matching executor found in allTools".
45
+
46
+ const appTools = new Map<string, Tool>();
47
+
48
+ /** Registra una tool de la aplicación para que entre en el loadout del agente. */
49
+ export function registerAppTool(tool: Tool): void {
50
+ appTools.set(tool.name, tool);
51
+ }
52
+
53
+ /** Quita todas las tools registradas por la app (útil entre tests). */
54
+ export function clearAppTools(): void {
55
+ appTools.clear();
56
+ }
57
+
58
+ /** Tools registradas por la app, en orden de registro. */
59
+ export function listAppTools(): Tool[] {
60
+ return Array.from(appTools.values());
61
+ }
46
62
 
47
63
  /**
48
- * Creates all 70 tools with proper configuration
64
+ * Creates all tools with proper configuration
49
65
  */
50
66
  export function createAllTools(config: Config): Tool[] {
51
67
  return [
52
68
  // FILESYSTEM (7)
53
69
  ...filesystem.createTools(),
54
70
 
55
- // WEB (9)
71
+ // WEB (10)
56
72
  ...web.createTools(),
57
73
 
58
- // PROJECTS (8)
59
- ...projects.createTools(),
60
-
61
- // CRON (7)
74
+ // CRON (8)
62
75
  ...cron.createTools(),
63
76
 
64
77
  // CLI (1)
65
78
  ...cli.createTools(),
66
79
 
67
- // AGENTS (14)
80
+ // AGENTS (15)
68
81
  ...agents.createTools(),
69
82
 
70
- // CANVAS (7 + A2UI 4)
71
- ...canvas.createTools(config),
72
-
73
- // CODEBRIDGE (3)
74
- ...codebridge.createTools(),
75
-
76
- // VOICE (2)
77
- ...voice.createTools(),
83
+ // A2UI (4)
84
+ ...a2ui.createTools(config),
78
85
 
79
86
  // CORE (4)
80
87
  ...core.createTools(),
@@ -82,8 +89,13 @@ export function createAllTools(config: Config): Tool[] {
82
89
  // OFFICE (8)
83
90
  ...office.createTools(),
84
91
 
85
- // MEETING (4)
86
- ...meeting.createTools(),
92
+ // API (1)
93
+ ...api.createTools(),
94
+
95
+ // Las de la app van últimas: si una comparte nombre con una nativa, gana la
96
+ // nativa, porque el selector busca por nombre y no queremos que registrar
97
+ // una tool desde afuera secuestre `fs_read` o `task_delegate`.
98
+ ...listAppTools(),
87
99
  ];
88
100
  }
89
101
 
@@ -96,26 +108,20 @@ export function createToolsByCategory(category: string, config: Config): Tool[]
96
108
  return filesystem.createTools();
97
109
  case "web":
98
110
  return web.createTools();
99
- case "projects":
100
- return projects.createTools();
101
111
  case "cron":
102
112
  return cron.createTools();
103
113
  case "cli":
104
114
  return cli.createTools();
105
115
  case "agents":
106
116
  return agents.createTools();
107
- case "canvas":
108
- return canvas.createTools(config);
109
- case "codebridge":
110
- return codebridge.createTools();
111
- case "voice":
112
- return voice.createTools();
117
+ case "a2ui":
118
+ return a2ui.createTools(config);
113
119
  case "core":
114
120
  return core.createTools();
115
121
  case "office":
116
122
  return office.createTools();
117
- case "meeting":
118
- return meeting.createTools();
123
+ case "api":
124
+ return api.createTools();
119
125
  default:
120
126
  return [];
121
127
  }
@@ -139,7 +145,6 @@ export {
139
145
  export {
140
146
  webSearchTool,
141
147
  webFetchTool,
142
- apiRequestTool,
143
148
  browserNavigateTool,
144
149
  browserScreenshotTool,
145
150
  browserClickTool,
@@ -147,19 +152,9 @@ export {
147
152
  browserExtractTool,
148
153
  browserScriptTool,
149
154
  browserWaitTool,
155
+ artifactInspectTool,
150
156
  } from "./web/index.ts";
151
157
 
152
- export {
153
- projectCreateTool,
154
- projectListTool,
155
- projectUpdateTool,
156
- projectDoneTool,
157
- projectFailTool,
158
- taskCreateTool,
159
- taskUpdateTool,
160
- taskEvaluateTool,
161
- } from "./projects/index.ts";
162
-
163
158
  export {
164
159
  cronCreateTool,
165
160
  cronListTool,
@@ -185,33 +180,19 @@ export {
185
180
  agentFindTool,
186
181
  agentArchiveTool,
187
182
  taskDelegateTool,
188
- taskDelegateCodeTool,
183
+ taskReviseTool,
184
+ taskListTool,
189
185
  taskStatusTool,
190
186
  busPublishTool,
191
187
  busReadTool,
192
- projectUpdatesTool,
193
188
  } from "./agents/index.ts";
194
189
 
195
190
  export {
196
- canvasRenderTool,
197
- canvasAskTool,
198
- canvasConfirmTool,
199
- canvasShowCardTool,
200
- canvasShowProgressTool,
201
- canvasShowListTool,
202
- canvasClearTool,
203
- } from "./canvas/index.ts";
204
-
205
- export {
206
- codebridgeLaunchTool,
207
- codebridgeStatusTool,
208
- codebridgeCancelTool,
209
- } from "./codebridge/index.ts";
210
-
211
- export {
212
- voiceTranscribeTool,
213
- voiceSpeakTool,
214
- } from "./voice/index.ts";
191
+ createA2UISurfaceTool,
192
+ createA2UIUpdateComponentsTool,
193
+ createA2UIUpdateDataModelTool,
194
+ createA2UIDeleteSurfaceTool,
195
+ } from "./a2ui/index.ts";
215
196
 
216
197
  export {
217
198
  searchKnowledgeTool,
@@ -230,3 +211,7 @@ export {
230
211
  officeLeerPptxTool,
231
212
  officeEscribirPptxTool,
232
213
  } from "./office/index.ts";
214
+
215
+ export {
216
+ apiRequestTool,
217
+ } from "./api/index.ts";
@@ -38,7 +38,9 @@ export const officeEscribirXlsxTool: Tool = {
38
38
  type: "object",
39
39
  properties: {
40
40
  nombre: { type: "string" },
41
- datos: { type: "array" },
41
+ // datos puede ser array de objetos o array de arrays (aoa) — el tool
42
+ // acepta ambos en runtime (ver Array.isArray(datos[0]) más abajo).
43
+ datos: { type: "array", items: { type: "object" } },
42
44
  encabezados: { type: "array", items: { type: "string" } },
43
45
  },
44
46
  },
@@ -2,7 +2,7 @@
2
2
  * Tool Type Definitions
3
3
  * Shared across all tool categories
4
4
  *
5
- * These types are used by all 52 native tools in Hive
5
+ * These types are shared by every native tool in Hive.
6
6
  */
7
7
 
8
8
  export interface Tool {
@@ -13,6 +13,8 @@ export interface Tool {
13
13
  properties: Record<string, ToolParameter>;
14
14
  required?: string[];
15
15
  };
16
+ /** Per-tool timeout (ms) override. Falls back to config.tools.timeouts[name] → workerPool.toolTimeoutMs. */
17
+ timeoutMs?: number;
16
18
  execute: (
17
19
  params: Record<string, unknown>,
18
20
  config?: any