@johpaz/hive-sdk 0.1.6 → 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 (185) hide show
  1. package/CHANGELOG.md +135 -0
  2. package/README.md +11 -1
  3. package/package.json +18 -2
  4. package/packages/core/src/agent/acceptance-checks.ts +9 -9
  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 +14 -5
  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 +141 -44
  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 +63 -384
  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 +460 -21
  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 -859
  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/acceptance-checks.test.ts +0 -403
  153. package/test/agent-loop-terminal-synthesis.test.ts +0 -32
  154. package/test/browser-backend.test.ts +0 -308
  155. package/test/catalog-agents-stay-enabled.test.ts +0 -117
  156. package/test/causal-events.test.ts +0 -117
  157. package/test/compaction.test.ts +0 -105
  158. package/test/context-compiler.test.ts +0 -269
  159. package/test/curator.test.ts +0 -130
  160. package/test/durable-queue.test.ts +0 -114
  161. package/test/harness-barrel.test.ts +0 -64
  162. package/test/hive-helpers.test.ts +0 -130
  163. package/test/hivedb-search.test.ts +0 -189
  164. package/test/internal-turns.test.ts +0 -166
  165. package/test/job-idempotency.test.ts +0 -68
  166. package/test/job-retry-backoff.test.ts +0 -184
  167. package/test/job-store.test.ts +0 -381
  168. package/test/llm-retry.test.ts +0 -97
  169. package/test/memory-perf.test.ts +0 -774
  170. package/test/minimal-loadout.test.ts +0 -78
  171. package/test/model-catalog.test.ts +0 -105
  172. package/test/preload.ts +0 -12
  173. package/test/reflector.test.ts +0 -320
  174. package/test/retention-cap.test.ts +0 -91
  175. package/test/retired-capabilities-pruned.test.ts +0 -192
  176. package/test/run-store.test.ts +0 -355
  177. package/test/scratchpad.test.ts +0 -74
  178. package/test/secrets-durability.test.ts +0 -119
  179. package/test/seed-model-reseed.test.ts +0 -155
  180. package/test/setup-agent-seed.test.ts +0 -264
  181. package/test/tool-inventory.test.ts +0 -65
  182. package/test/tool-runtime.test.ts +0 -258
  183. package/test/tool-selector-runtime-tools.test.ts +0 -117
  184. package/test/toon.test.ts +0 -429
  185. 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
+ };
@@ -1,23 +1,28 @@
1
1
  /**
2
2
  * BrowserBackend — el contrato que consumen las browser tools.
3
3
  *
4
- * Hay dos implementaciones con perfiles opuestos:
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`).
5
10
  *
6
- * - `AgentBrowserBackend` (browser-service.ts): habla con el CLI de
7
- * agent-browser por subproceso. Maneja Chrome de verdad, corre headless y
8
- * por lo tanto sirve en Docker y en un servidor sin display. El costo es
9
- * un `Bun.spawn` por operación —medido en ~68 ms de piso— más una
10
- * instalación diferida de ~75 MB y la descarga de Chrome.
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.
11
14
  *
12
- * - `WebViewBackend` (webview-backend.ts): usa `Bun.WebView`, in-process.
13
- * Sin instalación, sin subprocesos, ~0.25 ms por `evaluate`. Necesita un
14
- * entorno gráfico (WebKitGTK en Linux, WebKit del sistema en macOS), así
15
- * que no sirve headless.
16
- *
17
- * El default sigue siendo agent-browser: es el único de los dos que funciona
18
- * donde suele correr el gateway. WebView se elige explícitamente.
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.
19
20
  */
20
21
 
22
+ import { logger } from "../../utils/logger.ts";
23
+
24
+ const log = logger.child("browser-backend");
25
+
21
26
  export interface ScreenshotOptions {
22
27
  encoding?: "blob" | "buffer" | "base64" | "shmem";
23
28
  format?: "png" | "jpeg" | "webp";
@@ -62,55 +67,126 @@ export interface BrowserBackend {
62
67
  resize(width: number, height: number): Promise<void>;
63
68
  /** Captura de un elemento puntual; el backend puede recortar sobre el viewport. */
64
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>;
65
75
  close(): void;
66
76
  }
67
77
 
68
- export type BrowserBackendKind = "agent-browser" | "webview";
78
+ /** Ya no hay más de uno; el tipo se conserva porque lo reportan doctor y el gateway. */
79
+ export type BrowserBackendKind = "webview";
69
80
 
70
- /** `auto` resuelve a webview si el runtime lo soporta; si no, agent-browser. */
71
- export type BrowserBackendPreference = BrowserBackendKind | "auto";
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";
72
87
 
73
88
  /** Motor que usa `Bun.WebView` por debajo. */
74
89
  export type WebViewEngine = "webkit" | "chrome";
75
90
 
76
- const CHROME_PATHS = [
77
- "/usr/bin/google-chrome",
78
- "/usr/bin/google-chrome-stable",
79
- "/usr/bin/chromium",
80
- "/usr/bin/chromium-browser",
81
- "/snap/bin/chromium",
82
- "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
83
- "/Applications/Chromium.app/Contents/MacOS/Chromium",
84
- "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
85
- "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
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",
86
107
  ];
87
108
 
88
- /** Chrome instalado en el sistema `Bun.WebView` con motor chrome lo necesita. */
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. */
89
159
  export function findChrome(): string | null {
90
160
  if (process.env.BUN_CHROME_PATH) return process.env.BUN_CHROME_PATH;
161
+
91
162
  const { existsSync } = require("node:fs") as typeof import("node:fs");
92
- for (const candidate of CHROME_PATHS) {
93
- if (existsSync(candidate)) return candidate;
163
+ for (const candidate of chromePathsForPlatform()) {
164
+ if (candidate && existsSync(candidate)) return candidate;
94
165
  }
95
- // Último recurso: lo que esté en el PATH del usuario.
96
- const home = process.env.HOME;
97
- if (home) {
98
- for (const name of ["google-chrome", "chromium"]) {
99
- const local = `${home}/.local/bin/${name}`;
100
- if (existsSync(local)) return local;
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;
101
174
  }
102
175
  }
103
176
  return null;
104
177
  }
105
178
 
106
179
  /**
107
- * Motor a usar. WebKit no tiene dependencias externas pero sólo existe en macOS;
108
- * Chrome corre headless en cualquier lado —incluido Docker— siempre que el
109
- * binario esté instalado.
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.
110
186
  */
111
187
  export function resolveWebViewEngine(): WebViewEngine | null {
112
- if (process.platform === "darwin") return "webkit";
113
- return findChrome() ? "chrome" : null;
188
+ if (findChrome()) return "chrome";
189
+ return process.platform === "darwin" ? "webkit" : null;
114
190
  }
115
191
 
116
192
  /** `Bun.WebView` existe desde Bun 1.3; además hace falta un motor utilizable. */
@@ -119,11 +195,32 @@ export function isWebViewSupported(): boolean {
119
195
  return resolveWebViewEngine() !== null;
120
196
  }
121
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
+
122
214
  export function resolveBackendKind(
123
215
  preference: BrowserBackendPreference | undefined,
124
216
  ): BrowserBackendKind {
125
- const wanted = process.env.HIVE_BROWSER_BACKEND ?? preference ?? "agent-browser";
126
- if (wanted === "webview") return "webview";
127
- if (wanted === "auto") return isWebViewSupported() ? "webview" : "agent-browser";
128
- return "agent-browser";
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";
129
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);