@johpaz/hive-sdk 0.1.5 → 0.2.0

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 (182) hide show
  1. package/CHANGELOG.md +167 -0
  2. package/README.md +11 -1
  3. package/package.json +26 -10
  4. package/packages/core/src/agent/acceptance-checks.ts +16 -10
  5. package/packages/core/src/agent/agent-catalog.ts +3 -3
  6. package/packages/core/src/agent/agent-loop.ts +115 -26
  7. package/packages/core/src/agent/capability-search.ts +2 -2
  8. package/packages/core/src/agent/catalog-selector.ts +4 -4
  9. package/packages/core/src/agent/compaction.ts +10 -9
  10. package/packages/core/src/agent/context-compiler.ts +58 -34
  11. package/packages/core/src/agent/conversation-store.ts +31 -7
  12. package/packages/core/src/agent/curator.ts +4 -4
  13. package/packages/core/src/agent/delegation-runtime.ts +5 -5
  14. package/packages/core/src/agent/goal-runner.ts +9 -9
  15. package/packages/core/src/agent/index.ts +1 -0
  16. package/packages/core/src/agent/llm-client.ts +98 -37
  17. package/packages/core/src/agent/llm-providers/anthropic.ts +4 -4
  18. package/packages/core/src/agent/llm-providers/deepseek.ts +1 -1
  19. package/packages/core/src/agent/llm-providers/gemini.ts +4 -4
  20. package/packages/core/src/agent/llm-providers/groq.ts +1 -1
  21. package/packages/core/src/agent/llm-providers/hiveagents.ts +3 -3
  22. package/packages/core/src/agent/llm-providers/interface.ts +2 -2
  23. package/packages/core/src/agent/llm-providers/kimi.ts +1 -1
  24. package/packages/core/src/agent/llm-providers/minimax.ts +1 -1
  25. package/packages/core/src/agent/llm-providers/mistral.ts +1 -1
  26. package/packages/core/src/agent/llm-providers/modelscope.ts +1 -1
  27. package/packages/core/src/agent/llm-providers/nvidia.ts +1 -1
  28. package/packages/core/src/agent/llm-providers/ollama.ts +4 -4
  29. package/packages/core/src/agent/llm-providers/openai-compat-base.ts +9 -5
  30. package/packages/core/src/agent/llm-providers/openai.ts +1 -1
  31. package/packages/core/src/agent/llm-providers/opencode-go.ts +1 -1
  32. package/packages/core/src/agent/llm-providers/openrouter.ts +1 -1
  33. package/packages/core/src/agent/llm-providers/qwen.ts +1 -1
  34. package/packages/core/src/agent/llm-providers/z-ai.ts +1 -1
  35. package/packages/core/src/agent/mcp-result-normalizer.ts +192 -0
  36. package/packages/core/src/agent/playbook-selector.ts +4 -4
  37. package/packages/core/src/agent/prompt-builder.ts +5 -5
  38. package/packages/core/src/agent/proof-packet.ts +5 -5
  39. package/packages/core/src/agent/providers/index.ts +4 -4
  40. package/packages/core/src/agent/realtime-providers/gemini-live.ts +238 -0
  41. package/packages/core/src/agent/realtime-providers/index.ts +29 -0
  42. package/packages/core/src/agent/realtime-providers/interface.ts +108 -0
  43. package/packages/core/src/agent/reflector.ts +6 -6
  44. package/packages/core/src/agent/run-store.ts +8 -8
  45. package/packages/core/src/agent/service.ts +10 -10
  46. package/packages/core/src/agent/skill-selector.ts +6 -6
  47. package/packages/core/src/agent/thread-id.ts +71 -0
  48. package/packages/core/src/agent/thread-store.ts +250 -0
  49. package/packages/core/src/agent/tool-selector.ts +7 -5
  50. package/packages/core/src/agent/tracer.ts +5 -5
  51. package/packages/core/src/api/createAgent.ts +1 -1
  52. package/packages/core/src/artifacts/store.ts +84 -3
  53. package/packages/core/src/canvas/emitter.ts +2 -2
  54. package/packages/core/src/channels/telegram.ts +1 -1
  55. package/packages/core/src/channels/webchat.ts +1 -1
  56. package/packages/core/src/config/loader.ts +15 -1
  57. package/packages/core/src/events/agent-bus.ts +3 -3
  58. package/packages/core/src/events/channel-narration.ts +3 -3
  59. package/packages/core/src/events/event-bus.ts +1 -1
  60. package/packages/core/src/events/narration.ts +3 -3
  61. package/packages/core/src/gateway/delegation-groups.ts +4 -4
  62. package/packages/core/src/gateway/durable-queue.ts +5 -5
  63. package/packages/core/src/gateway/job-store.ts +5 -5
  64. package/packages/core/src/gateway/notification-inbox.ts +2 -2
  65. package/packages/core/src/gateway/server.ts +2 -2
  66. package/packages/core/src/mcp/MCPClient.ts +3 -3
  67. package/packages/core/src/mcp/hot-reload.ts +5 -5
  68. package/packages/core/src/mcp/tool-sync.ts +5 -5
  69. package/packages/core/src/mcp/transports/index.ts +2 -2
  70. package/packages/core/src/mcp/transports/sse.ts +1 -1
  71. package/packages/core/src/models/index.ts +36 -0
  72. package/packages/core/src/multimodal/index.ts +2 -2
  73. package/packages/core/src/multimodal/vision-service.ts +6 -6
  74. package/packages/core/src/plugins/loader.ts +4 -1
  75. package/packages/core/src/resilience/circuit-breaker.ts +16 -5
  76. package/packages/core/src/resilience/retry.ts +1 -1
  77. package/packages/core/src/scheduler/CronScheduler.ts +6 -6
  78. package/packages/core/src/scheduler/integration.ts +9 -9
  79. package/packages/core/src/sessions/index.ts +266 -0
  80. package/packages/core/src/storage/bootstrap.ts +34 -8
  81. package/packages/core/src/storage/causal-events.ts +1 -1
  82. package/packages/core/src/storage/collections.ts +32 -1
  83. package/packages/core/src/storage/crypto.ts +16 -2
  84. package/packages/core/src/storage/hive.ts +1 -1
  85. package/packages/core/src/storage/hivedb.ts +10 -1
  86. package/packages/core/src/storage/onboarding.ts +6 -6
  87. package/packages/core/src/storage/reconcile.ts +5 -5
  88. package/packages/core/src/storage/seed.ts +103 -13
  89. package/packages/core/src/storage/usage.ts +3 -3
  90. package/packages/core/src/swarm/AgentExecutor.ts +2 -2
  91. package/packages/core/src/swarm/Coordinator.ts +8 -8
  92. package/packages/core/src/swarm/EventBridge.ts +2 -2
  93. package/packages/core/src/swarm/RoleSwarm.ts +234 -0
  94. package/packages/core/src/swarm/TaskGraph.ts +2 -2
  95. package/packages/core/src/swarm/index.ts +7 -0
  96. package/packages/core/src/swarm/presets/HiveLearnPreset.ts +2 -2
  97. package/packages/core/src/swarm/presets/ResearchPreset.ts +2 -2
  98. package/packages/core/src/swarm/strategies/ParallelStrategy.ts +1 -1
  99. package/packages/core/src/swarm/strategies/PriorityStrategy.ts +3 -3
  100. package/packages/core/src/tools/ToolExecutor.ts +7 -3
  101. package/packages/core/src/tools/core/index.ts +2 -2
  102. package/packages/core/src/tools/cron/index.ts +4 -4
  103. package/packages/core/src/tools/web/artifact-inspect.ts +2 -2
  104. package/packages/core/src/tools/web/artifact-read.ts +162 -0
  105. package/packages/core/src/tools/web/browser-backend.ts +226 -0
  106. package/packages/core/src/tools/web/browser-click.ts +2 -2
  107. package/packages/core/src/tools/web/browser-extract.ts +2 -2
  108. package/packages/core/src/tools/web/browser-navigate.ts +2 -2
  109. package/packages/core/src/tools/web/browser-screenshot.ts +12 -5
  110. package/packages/core/src/tools/web/browser-script.ts +2 -2
  111. package/packages/core/src/tools/web/browser-service.ts +85 -366
  112. package/packages/core/src/tools/web/browser-session.ts +125 -0
  113. package/packages/core/src/tools/web/browser-type.ts +2 -2
  114. package/packages/core/src/tools/web/browser-wait.ts +2 -2
  115. package/packages/core/src/tools/web/computer-use.ts +553 -0
  116. package/packages/core/src/tools/web/index.ts +8 -1
  117. package/packages/core/src/tools/web/webview-backend.ts +851 -0
  118. package/packages/core/src/utils/index.ts +1 -0
  119. package/packages/core/src/utils/logger.ts +12 -4
  120. package/packages/core/src/utils/redact-binary.ts +17 -0
  121. package/packages/core/src/utils/toon.ts +1 -1
  122. package/packages/core/src/voice/index.ts +6 -6
  123. package/bun.lock +0 -833
  124. package/bunfig.toml +0 -9
  125. package/docs/API-AGENTS.md +0 -367
  126. package/docs/API-CONTEXT-COMPILER.md +0 -249
  127. package/docs/API-DAG-SCHEDULER.md +0 -273
  128. package/docs/API-TOOLS-SKILLS-CHANNELS.md +0 -446
  129. package/docs/API-WORKERS-EVENTS.md +0 -299
  130. package/docs/HIVE-HARNESS.md +0 -113
  131. package/docs/INDEX.md +0 -190
  132. package/docs/TEMPLATE-HIVE-APP.md +0 -360
  133. package/packages/cli/package.json +0 -17
  134. package/packages/cli/src/commands/create-app.test.ts +0 -180
  135. package/packages/core/package.json +0 -70
  136. package/packages/core/src/api/createAgent.test.ts +0 -160
  137. package/packages/core/src/canvas/canvas.test.ts +0 -36
  138. package/packages/core/src/channels/channels.test.ts +0 -18
  139. package/packages/core/src/ethics/EthicsGuard.test.ts +0 -108
  140. package/packages/core/src/gateway/gateway.test.ts +0 -38
  141. package/packages/core/src/memory/Scratchpad.test.ts +0 -68
  142. package/packages/core/src/scheduler/scheduler.test.ts +0 -15
  143. package/packages/core/src/skills/skills.test.ts +0 -62
  144. package/packages/core/src/swarm/swarm.test.ts +0 -24
  145. package/packages/core/src/tool-runtime/tool-runtime.test.ts +0 -99
  146. package/packages/core/src/tools/ToolRegistry.test.ts +0 -98
  147. package/packages/core/src/tools/api/api-request.test.ts +0 -164
  148. package/packages/core/src/tools/web/browser-service.test.ts +0 -83
  149. package/packages/core/src/workers/workers.test.ts +0 -41
  150. package/scripts/bump-version.ts +0 -248
  151. package/scripts/generate-skill-bundle.ts +0 -108
  152. package/test/agent-loop-terminal-synthesis.test.ts +0 -32
  153. package/test/catalog-agents-stay-enabled.test.ts +0 -117
  154. package/test/causal-events.test.ts +0 -117
  155. package/test/compaction.test.ts +0 -105
  156. package/test/context-compiler.test.ts +0 -269
  157. package/test/curator.test.ts +0 -130
  158. package/test/durable-queue.test.ts +0 -114
  159. package/test/harness-barrel.test.ts +0 -64
  160. package/test/hive-helpers.test.ts +0 -130
  161. package/test/hivedb-search.test.ts +0 -189
  162. package/test/internal-turns.test.ts +0 -166
  163. package/test/job-idempotency.test.ts +0 -68
  164. package/test/job-retry-backoff.test.ts +0 -184
  165. package/test/job-store.test.ts +0 -381
  166. package/test/llm-retry.test.ts +0 -97
  167. package/test/memory-perf.test.ts +0 -774
  168. package/test/minimal-loadout.test.ts +0 -78
  169. package/test/model-catalog.test.ts +0 -105
  170. package/test/preload.ts +0 -12
  171. package/test/reflector.test.ts +0 -320
  172. package/test/retention-cap.test.ts +0 -91
  173. package/test/retired-capabilities-pruned.test.ts +0 -192
  174. package/test/run-store.test.ts +0 -355
  175. package/test/scratchpad.test.ts +0 -74
  176. package/test/secrets-durability.test.ts +0 -119
  177. package/test/seed-model-reseed.test.ts +0 -155
  178. package/test/setup-agent-seed.test.ts +0 -264
  179. package/test/tool-inventory.test.ts +0 -65
  180. package/test/tool-runtime.test.ts +0 -258
  181. package/test/toon.test.ts +0 -429
  182. package/tsconfig.json +0 -42
@@ -1,5 +1,5 @@
1
- import type { Tool } from "../types";
2
- import { inspectArtifact } from "../../artifacts/store";
1
+ import type { Tool } from "../types.ts";
2
+ import { inspectArtifact } from "../../artifacts/store.ts";
3
3
 
4
4
  export const artifactInspectTool: Tool = {
5
5
  name: "artifact_inspect",
@@ -0,0 +1,162 @@
1
+ /**
2
+ * artifact_read — reads back the content an artifact_ref points at.
3
+ *
4
+ * mcp-result-normalizer.ts keeps oversized MCP text results out of the context
5
+ * window: the model gets `{ type: "artifact_ref", artifact_id, size, preview }`
6
+ * with a blind 500-char preview. That is the right call for the context, but
7
+ * without a way to read the artifact back it left the agent stuck — it would
8
+ * call artifact_inspect (metadata only), find no content, and burn its
9
+ * iterations until the turn died on an empty synthesis. This is the way back in.
10
+ *
11
+ * Slicing is by characters, not lines: the case this was built for is a single
12
+ * 245 KB JSON line from a Gmail MCP server, where line-based paging returns
13
+ * everything or nothing. `search` exists for the same reason — jumping straight
14
+ * to the three relevant messages beats paging through 245 KB in 20 KB chunks.
15
+ */
16
+
17
+ import type { Tool } from "../types.ts";
18
+ import { readArtifactText } from "../../artifacts/store.ts";
19
+
20
+ const DEFAULT_LIMIT = 20_000;
21
+ const MAX_LIMIT = 50_000;
22
+ const MAX_MATCHES = 10;
23
+ const DEFAULT_MATCH_CONTEXT = 600;
24
+ const MAX_MATCH_CONTEXT = 8_000;
25
+ /**
26
+ * What a search may return in total. Capping the per-match window alone would
27
+ * be the wrong knob: a record whose fields are 2 KB of mail headers needs a
28
+ * wide window to show its id, while ten such windows would put back into the
29
+ * context exactly what the artifact was created to keep out.
30
+ */
31
+ const MAX_SEARCH_CHARS = 40_000;
32
+
33
+ function clampNumber(value: unknown, fallback: number, min: number, max: number): number {
34
+ const parsed = typeof value === "number" ? value : Number(value);
35
+ if (!Number.isFinite(parsed)) return fallback;
36
+ return Math.min(max, Math.max(min, Math.floor(parsed)));
37
+ }
38
+
39
+ function findMatches(text: string, query: string, context: number): Array<{ offset: number; excerpt: string }> {
40
+ const haystack = text.toLowerCase();
41
+ const needle = query.toLowerCase();
42
+ const matches: Array<{ offset: number; excerpt: string }> = [];
43
+ const half = Math.floor(context / 2);
44
+ let from = 0;
45
+
46
+ let used = 0;
47
+
48
+ while (matches.length < MAX_MATCHES) {
49
+ const at = haystack.indexOf(needle, from);
50
+ if (at === -1) break;
51
+ const start = Math.max(0, at - half);
52
+ const end = Math.min(text.length, at + needle.length + half);
53
+ const excerpt = text.slice(start, end);
54
+ // The first match always comes back, however wide it is — returning zero
55
+ // results for a query that did match would be the worst possible answer.
56
+ if (matches.length > 0 && used + excerpt.length > MAX_SEARCH_CHARS) break;
57
+ matches.push({ offset: at, excerpt });
58
+ used += excerpt.length;
59
+ from = at + needle.length;
60
+ }
61
+
62
+ return matches;
63
+ }
64
+
65
+ function countMatches(text: string, query: string): number {
66
+ const haystack = text.toLowerCase();
67
+ const needle = query.toLowerCase();
68
+ let total = 0;
69
+ let from = 0;
70
+ for (;;) {
71
+ const at = haystack.indexOf(needle, from);
72
+ if (at === -1) return total;
73
+ total++;
74
+ from = at + needle.length;
75
+ }
76
+ }
77
+
78
+ export const artifactReadTool: Tool = {
79
+ name: "artifact_read",
80
+ description:
81
+ "Read the text content of a managed artifact in slices, or search inside it. Use this on any artifact_ref a tool returned instead of guessing from its preview. Spanish: leer artefacto, ver contenido del artefacto, abrir resultado grande, buscar dentro del artefacto",
82
+ parameters: {
83
+ type: "object",
84
+ properties: {
85
+ artifactId: {
86
+ type: "string",
87
+ description: "Managed artifact identifier (the artifact_id of an artifact_ref).",
88
+ },
89
+ offset: {
90
+ type: "number",
91
+ description: "Character position to start reading from (default: 0). Use next_offset from the previous call to continue.",
92
+ },
93
+ limit: {
94
+ type: "number",
95
+ description: `Characters to return (default: ${DEFAULT_LIMIT}, max: ${MAX_LIMIT}).`,
96
+ },
97
+ search: {
98
+ type: "string",
99
+ description: "Return excerpts around each case-insensitive match instead of a contiguous slice. Far cheaper than paging a large artifact.",
100
+ },
101
+ context: {
102
+ type: "number",
103
+ description: `Characters of surrounding text per match (default: ${DEFAULT_MATCH_CONTEXT}, max: ${MAX_MATCH_CONTEXT}). Widen it when records carry long fields and the excerpt cuts off the data you need.`,
104
+ },
105
+ },
106
+ required: ["artifactId"],
107
+ },
108
+ execute: async (params, config) => {
109
+ const artifactId = String(params.artifactId ?? "").trim();
110
+ if (!artifactId) return { ok: false, error: "artifactId is required" };
111
+
112
+ const userId = config?.configurable?.user_id as string | undefined;
113
+ const result = await readArtifactText(artifactId, { userId });
114
+ if (!result.ok || !result.artifact) {
115
+ return { ok: false, artifact_id: artifactId, error: result.error, status: result.status };
116
+ }
117
+
118
+ const artifact = result.artifact;
119
+ const text = result.text ?? "";
120
+ const search = typeof params.search === "string" ? params.search.trim() : "";
121
+
122
+ if (search) {
123
+ const context = clampNumber(params.context, DEFAULT_MATCH_CONTEXT, 40, MAX_MATCH_CONTEXT);
124
+ const matches = findMatches(text, search, context);
125
+ // Always the real count, never the number returned: collection stops at
126
+ // MAX_MATCHES or at the character budget, and an agent told "4 matches"
127
+ // when there are 8 stops looking.
128
+ const total = countMatches(text, search);
129
+ return {
130
+ ok: true,
131
+ artifact_id: artifact.id,
132
+ mime_type: artifact.mime_type,
133
+ total_chars: text.length,
134
+ query: search,
135
+ total_matches: total,
136
+ returned_matches: matches.length,
137
+ truncated: total > matches.length,
138
+ context,
139
+ matches,
140
+ };
141
+ }
142
+
143
+ const offset = clampNumber(params.offset, 0, 0, Math.max(0, text.length));
144
+ const limit = clampNumber(params.limit, DEFAULT_LIMIT, 1, MAX_LIMIT);
145
+ const content = text.slice(offset, offset + limit);
146
+ const nextOffset = offset + content.length;
147
+ const eof = nextOffset >= text.length;
148
+
149
+ return {
150
+ ok: true,
151
+ artifact_id: artifact.id,
152
+ mime_type: artifact.mime_type,
153
+ kind: artifact.kind,
154
+ total_chars: text.length,
155
+ offset,
156
+ returned_chars: content.length,
157
+ next_offset: eof ? null : nextOffset,
158
+ eof,
159
+ content,
160
+ };
161
+ },
162
+ };
@@ -0,0 +1,226 @@
1
+ /**
2
+ * BrowserBackend — el contrato que consumen las browser tools.
3
+ *
4
+ * Hay una sola implementación: `WebViewBackend` (webview-backend.ts), sobre
5
+ * `Bun.WebView`. In-process, sin instalación y sin subprocesos: ~0,3 ms un
6
+ * `evaluate`, ~2 ms un `snapshot`. Con motor chrome corre headless de verdad
7
+ * —Bun lanza el navegador con `--headless`—, así que sirve en un servidor sin
8
+ * display; lo único que necesita es un navegador Chromium instalado (o
9
+ * `BUN_CHROME_PATH`).
10
+ *
11
+ * El contrato sigue existiendo aunque el backend sea uno solo: es lo que hace
12
+ * que las tools no dependan del motor, y lo que deja al motor WebKit de macOS
13
+ * —que no tiene CDP— entrar por la misma puerta con menos capacidades.
14
+ *
15
+ * Dos cosas que hay que saber del WebView y que están resueltas adentro: el
16
+ * perfil de Chrome que abre Bun es efímero (`/tmp/.<hash>.bun-chrome`, y el
17
+ * hash cambia entre procesos), por eso las cookies se guardan y restauran a
18
+ * mano desde `browser-session.ts`; y como root sin `--no-sandbox` Chromium no
19
+ * arranca, por eso el Dockerfile apunta `BUN_CHROME_PATH` a un wrapper.
20
+ */
21
+
22
+ import { logger } from "../../utils/logger.ts";
23
+
24
+ const log = logger.child("browser-backend");
25
+
26
+ export interface ScreenshotOptions {
27
+ encoding?: "blob" | "buffer" | "base64" | "shmem";
28
+ format?: "png" | "jpeg" | "webp";
29
+ quality?: number;
30
+ clip?: { x: number; y: number; width: number; height: number; scale: number };
31
+ }
32
+
33
+ export interface SnapshotOptions {
34
+ /** Colapsa nodos sin nombre accesible y recorta el texto largo. Default: true. */
35
+ compact?: boolean;
36
+ /** Profundidad máxima del árbol. */
37
+ depth?: number;
38
+ /** Sólo elementos accionables (links, botones, inputs...). */
39
+ interactiveOnly?: boolean;
40
+ }
41
+
42
+ /**
43
+ * La superficie que las tools de `tools/web/` realmente usan. Se mantuvo
44
+ * deliberadamente chica: cualquier método que se agregue acá hay que
45
+ * implementarlo en los dos backends.
46
+ */
47
+ export interface BrowserBackend {
48
+ readonly url: string;
49
+ readonly title: string;
50
+ readonly loading: boolean;
51
+
52
+ navigate(url: string): Promise<void>;
53
+ evaluate<T = unknown>(script: string): Promise<T>;
54
+ screenshot(options?: ScreenshotOptions): Promise<string>;
55
+ /** Árbol de accesibilidad en texto — lo que ve el modelo en vez del HTML crudo. */
56
+ snapshot(options?: SnapshotOptions): Promise<string>;
57
+ click(selector: string, options?: Record<string, unknown>): Promise<void>;
58
+ type(text: string): Promise<void>;
59
+ typeIn(selector: string, text: string): Promise<void>;
60
+ fill(selector: string, text: string): Promise<void>;
61
+ press(key: string, options?: { modifiers?: string[] }): Promise<void>;
62
+ scroll(dx: number, dy: number): Promise<void>;
63
+ scrollTo(selector: string, options?: { behavior?: "smooth" | "instant" }): Promise<void>;
64
+ back(): Promise<void>;
65
+ forward(): Promise<void>;
66
+ reload(): Promise<void>;
67
+ resize(width: number, height: number): Promise<void>;
68
+ /** Captura de un elemento puntual; el backend puede recortar sobre el viewport. */
69
+ screenshotElement(selector: string): Promise<string>;
70
+ /**
71
+ * Puente CDP crudo. Opcional a propósito: lo tienen los backends con Chrome,
72
+ * no el motor WebKit de macOS. Quien lo use tiene que traer una alternativa.
73
+ */
74
+ cdp?<T = unknown>(method: string, params?: Record<string, unknown>): Promise<T>;
75
+ close(): void;
76
+ }
77
+
78
+ /** Ya no hay más de uno; el tipo se conserva porque lo reportan doctor y el gateway. */
79
+ export type BrowserBackendKind = "webview";
80
+
81
+ /**
82
+ * Lo que se puede pedir por config. `"agent-browser"` era el backend por CLI que
83
+ * se retiró: se sigue aceptando para no romper un `hive.json` viejo, pero avisa
84
+ * y usa el WebView.
85
+ */
86
+ export type BrowserBackendPreference = BrowserBackendKind | "auto" | "agent-browser";
87
+
88
+ /** Motor que usa `Bun.WebView` por debajo. */
89
+ export type WebViewEngine = "webkit" | "chrome";
90
+
91
+ /**
92
+ * Binarios que valen: cualquier Chromium sirve, porque lo que Bun necesita es
93
+ * `--remote-debugging-pipe`, que todos implementan. Edge y Brave son los que más
94
+ * aparecen en máquinas donde no hay Chrome "de marca".
95
+ */
96
+ const CHROME_BINARIES = [
97
+ "google-chrome",
98
+ "google-chrome-stable",
99
+ "chromium",
100
+ "chromium-browser",
101
+ "brave-browser",
102
+ "microsoft-edge",
103
+ "microsoft-edge-stable",
104
+ "vivaldi",
105
+ "vivaldi-stable",
106
+ "opera",
107
+ ];
108
+
109
+ /** Rutas conocidas por plataforma, en orden de preferencia. */
110
+ function chromePathsForPlatform(): string[] {
111
+ const home = process.env.HOME ?? "";
112
+
113
+ if (process.platform === "darwin") {
114
+ return [
115
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
116
+ "/Applications/Chromium.app/Contents/MacOS/Chromium",
117
+ "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
118
+ "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
119
+ "/Applications/Vivaldi.app/Contents/MacOS/Vivaldi",
120
+ `${home}/Applications/Google Chrome.app/Contents/MacOS/Google Chrome`,
121
+ ];
122
+ }
123
+
124
+ if (process.platform === "win32") {
125
+ const programFiles = process.env["ProgramFiles"] ?? "C:\\Program Files";
126
+ const programFilesX86 = process.env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)";
127
+ const localAppData = process.env.LOCALAPPDATA ?? "";
128
+ return [
129
+ `${programFiles}\\Google\\Chrome\\Application\\chrome.exe`,
130
+ `${programFilesX86}\\Google\\Chrome\\Application\\chrome.exe`,
131
+ `${localAppData}\\Google\\Chrome\\Application\\chrome.exe`,
132
+ `${programFilesX86}\\Microsoft\\Edge\\Application\\msedge.exe`,
133
+ `${programFiles}\\Microsoft\\Edge\\Application\\msedge.exe`,
134
+ `${programFiles}\\BraveSoftware\\Brave-Browser\\Application\\brave.exe`,
135
+ ];
136
+ }
137
+
138
+ // Linux: paquetes del sistema, snap y los wrappers que exporta flatpak —que
139
+ // en la práctica son el único Chrome de muchas máquinas.
140
+ const flatpakDirs = ["/var/lib/flatpak/exports/bin", `${home}/.local/share/flatpak/exports/bin`];
141
+ const flatpakApps = ["com.google.Chrome", "org.chromium.Chromium", "com.brave.Browser", "com.microsoft.Edge"];
142
+ return [
143
+ "/usr/bin/google-chrome",
144
+ "/usr/bin/google-chrome-stable",
145
+ "/usr/bin/chromium",
146
+ "/usr/bin/chromium-browser",
147
+ "/usr/bin/brave-browser",
148
+ "/usr/bin/microsoft-edge",
149
+ "/usr/bin/microsoft-edge-stable",
150
+ "/usr/bin/vivaldi",
151
+ "/usr/bin/opera",
152
+ "/snap/bin/chromium",
153
+ "/snap/bin/brave",
154
+ ...flatpakDirs.flatMap((dir) => flatpakApps.map((app) => `${dir}/${app}`)),
155
+ ];
156
+ }
157
+
158
+ /** Navegador instalado — `Bun.WebView` con motor chrome lo necesita. */
159
+ export function findChrome(): string | null {
160
+ if (process.env.BUN_CHROME_PATH) return process.env.BUN_CHROME_PATH;
161
+
162
+ const { existsSync } = require("node:fs") as typeof import("node:fs");
163
+ for (const candidate of chromePathsForPlatform()) {
164
+ if (candidate && existsSync(candidate)) return candidate;
165
+ }
166
+
167
+ // Último recurso: lo que haya en el PATH, que cubre instalaciones a mano y
168
+ // los wrappers que el usuario tenga en ~/.local/bin.
169
+ const which = (globalThis as { Bun?: { which?: (cmd: string) => string | null } }).Bun?.which;
170
+ if (which) {
171
+ for (const name of CHROME_BINARIES) {
172
+ const found = which(name);
173
+ if (found) return found;
174
+ }
175
+ }
176
+ return null;
177
+ }
178
+
179
+ /**
180
+ * Motor a usar.
181
+ *
182
+ * Chrome primero **también en macOS**: es el único que trae CDP, y de CDP
183
+ * dependen el árbol de accesibilidad del snapshot, la sesión persistente y los
184
+ * clics reales de `computer_use_task`. WebKit queda como respaldo para un mac
185
+ * sin ningún Chromium instalado: no necesita nada, pero da menos.
186
+ */
187
+ export function resolveWebViewEngine(): WebViewEngine | null {
188
+ if (findChrome()) return "chrome";
189
+ return process.platform === "darwin" ? "webkit" : null;
190
+ }
191
+
192
+ /** `Bun.WebView` existe desde Bun 1.3; además hace falta un motor utilizable. */
193
+ export function isWebViewSupported(): boolean {
194
+ if (typeof (globalThis as { Bun?: { WebView?: unknown } }).Bun?.WebView !== "function") return false;
195
+ return resolveWebViewEngine() !== null;
196
+ }
197
+
198
+ /**
199
+ * Qué decirle a alguien que no tiene navegador. Lo usan el arranque del gateway
200
+ * y `hive doctor`, para que el mensaje sea el mismo en los dos lados.
201
+ */
202
+ export function browserInstallHint(): string {
203
+ if (process.platform === "darwin") {
204
+ return "Instala Google Chrome (brew install --cask google-chrome) o define BUN_CHROME_PATH.";
205
+ }
206
+ if (process.platform === "win32") {
207
+ return "Instala Google Chrome o Microsoft Edge, o define BUN_CHROME_PATH.";
208
+ }
209
+ return "Instala Chromium o Chrome (apt install chromium · dnf install chromium) o define BUN_CHROME_PATH.";
210
+ }
211
+
212
+ let avisoBackendRetirado = false;
213
+
214
+ export function resolveBackendKind(
215
+ preference: BrowserBackendPreference | undefined,
216
+ ): BrowserBackendKind {
217
+ const wanted = process.env.HIVE_BROWSER_BACKEND ?? preference;
218
+ if (wanted === "agent-browser" && !avisoBackendRetirado) {
219
+ avisoBackendRetirado = true;
220
+ log.warn(
221
+ 'tools.browser.backend: "agent-browser" ya no existe — se usa Bun.WebView. ' +
222
+ "Puedes quitar esa clave de la config.",
223
+ );
224
+ }
225
+ return "webview";
226
+ }
@@ -43,7 +43,7 @@ export const browserClickTool: Tool = {
43
43
  log.warn("Browser not available");
44
44
  return {
45
45
  ok: false,
46
- error: "Browser automation not available. Install agent-browser.",
46
+ error: "Browser automation not available. Install Chrome or Chromium (or run `hive doctor`).",
47
47
  };
48
48
  }
49
49
 
@@ -51,7 +51,7 @@ export const browserClickTool: Tool = {
51
51
 
52
52
  try {
53
53
  const view = await browserService.getView();
54
- if (!view) return { ok: false, error: "Browser automation not available. Install agent-browser." };
54
+ if (!view) return { ok: false, error: "Browser automation not available. Install Chrome or Chromium (or run `hive doctor`)." };
55
55
 
56
56
  if (url) {
57
57
  await view.navigate(url);
@@ -53,7 +53,7 @@ export const browserExtractTool: Tool = {
53
53
  log.warn("Browser not available");
54
54
  return {
55
55
  ok: false,
56
- error: "Browser automation not available. Install agent-browser.",
56
+ error: "Browser automation not available. Install Chrome or Chromium (or run `hive doctor`).",
57
57
  };
58
58
  }
59
59
 
@@ -61,7 +61,7 @@ export const browserExtractTool: Tool = {
61
61
 
62
62
  try {
63
63
  const view = await browserService.getView();
64
- if (!view) return { ok: false, error: "Browser automation not available. Install agent-browser." };
64
+ if (!view) return { ok: false, error: "Browser automation not available. Install Chrome or Chromium (or run `hive doctor`)." };
65
65
 
66
66
  if (url) {
67
67
  await view.navigate(url);
@@ -49,7 +49,7 @@ export const browserNavigateTool: Tool = {
49
49
  log.warn("Browser not available");
50
50
  return {
51
51
  ok: false,
52
- error: "Browser automation not available. Install agent-browser.",
52
+ error: "Browser automation not available. Install Chrome or Chromium (or run `hive doctor`).",
53
53
  };
54
54
  }
55
55
 
@@ -57,7 +57,7 @@ export const browserNavigateTool: Tool = {
57
57
 
58
58
  try {
59
59
  const view = await browserService.getView();
60
- if (!view) return { ok: false, error: "Browser automation not available. Install agent-browser." };
60
+ if (!view) return { ok: false, error: "Browser automation not available. Install Chrome or Chromium (or run `hive doctor`)." };
61
61
 
62
62
  await view.navigate(url);
63
63
 
@@ -68,7 +68,7 @@ export const browserScreenshotTool: Tool = {
68
68
  log.warn("Browser not available");
69
69
  return {
70
70
  ok: false,
71
- error: "Browser automation not available. Install agent-browser.",
71
+ error: "Browser automation not available. Install Chrome or Chromium (or run `hive doctor`).",
72
72
  };
73
73
  }
74
74
 
@@ -76,16 +76,23 @@ export const browserScreenshotTool: Tool = {
76
76
 
77
77
  try {
78
78
  const view = await browserService.getView();
79
- if (!view) return { ok: false, error: "Browser automation not available. Install agent-browser." };
79
+ if (!view) return { ok: false, error: "Browser automation not available. Install Chrome or Chromium (or run `hive doctor`)." };
80
80
 
81
81
  if (url) {
82
82
  await view.navigate(url);
83
83
  await Bun.sleep(500);
84
84
  }
85
85
 
86
- // Resize viewport before screenshot to keep image small
87
- await view.resize(width, height);
88
- await Bun.sleep(200);
86
+ // Fijar el viewport es un intento, no un requisito: `Emulation.setDevice
87
+ // MetricsOverride` no existe en todos los motores —y falla si todavía no
88
+ // se navegó a ninguna página—. Sin esto, un resize que no se puede hacer
89
+ // tiraba abajo la captura entera.
90
+ try {
91
+ await view.resize(width, height);
92
+ await Bun.sleep(200);
93
+ } catch (error) {
94
+ log.info(`el motor no permite fijar el viewport: ${(error as Error).message}`);
95
+ }
89
96
 
90
97
  let screenshot: string;
91
98
 
@@ -43,7 +43,7 @@ export const browserScriptTool: Tool = {
43
43
  log.warn("Browser not available");
44
44
  return {
45
45
  ok: false,
46
- error: "Browser automation not available. Install agent-browser.",
46
+ error: "Browser automation not available. Install Chrome or Chromium (or run `hive doctor`).",
47
47
  };
48
48
  }
49
49
 
@@ -51,7 +51,7 @@ export const browserScriptTool: Tool = {
51
51
 
52
52
  try {
53
53
  const view = await browserService.getView();
54
- if (!view) return { ok: false, error: "Browser automation not available. Install agent-browser." };
54
+ if (!view) return { ok: false, error: "Browser automation not available. Install Chrome or Chromium (or run `hive doctor`)." };
55
55
 
56
56
  if (url) {
57
57
  await view.navigate(url);