@johpaz/hive-sdk 0.1.6 → 0.3.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.
- package/CHANGELOG.md +441 -0
- package/README.md +21 -3
- package/package.json +27 -5
- package/packages/core/src/agent/acceptance-checks.ts +9 -9
- package/packages/core/src/agent/agent-catalog.ts +82 -25
- package/packages/core/src/agent/agent-loop.ts +115 -26
- package/packages/core/src/agent/capability-search.ts +2 -2
- package/packages/core/src/agent/catalog-selector.ts +4 -4
- package/packages/core/src/agent/compaction.ts +30 -10
- package/packages/core/src/agent/context-compiler.ts +63 -36
- package/packages/core/src/agent/conversation-store.ts +167 -9
- package/packages/core/src/agent/curator.ts +16 -7
- package/packages/core/src/agent/delegation-runtime.ts +5 -5
- package/packages/core/src/agent/goal-runner.ts +9 -9
- package/packages/core/src/agent/index.ts +1 -0
- package/packages/core/src/agent/llm-client.ts +98 -37
- package/packages/core/src/agent/llm-providers/anthropic.ts +4 -4
- package/packages/core/src/agent/llm-providers/deepseek.ts +1 -1
- package/packages/core/src/agent/llm-providers/gemini.ts +4 -4
- package/packages/core/src/agent/llm-providers/groq.ts +1 -1
- package/packages/core/src/agent/llm-providers/hiveagents.ts +3 -3
- package/packages/core/src/agent/llm-providers/interface.ts +2 -2
- package/packages/core/src/agent/llm-providers/kimi.ts +1 -1
- package/packages/core/src/agent/llm-providers/minimax.ts +1 -1
- package/packages/core/src/agent/llm-providers/mistral.ts +1 -1
- package/packages/core/src/agent/llm-providers/modelscope.ts +1 -1
- package/packages/core/src/agent/llm-providers/nvidia.ts +40 -1
- package/packages/core/src/agent/llm-providers/ollama.ts +4 -4
- package/packages/core/src/agent/llm-providers/openai-compat-base.ts +47 -7
- package/packages/core/src/agent/llm-providers/openai.ts +1 -1
- package/packages/core/src/agent/llm-providers/opencode-go.ts +1 -1
- package/packages/core/src/agent/llm-providers/openrouter.ts +1 -1
- package/packages/core/src/agent/llm-providers/qwen.ts +1 -1
- package/packages/core/src/agent/llm-providers/z-ai.ts +1 -1
- package/packages/core/src/agent/mcp-result-normalizer.ts +192 -0
- package/packages/core/src/agent/playbook-selector.ts +22 -7
- package/packages/core/src/agent/prompt-builder.ts +5 -5
- package/packages/core/src/agent/proof-packet.ts +5 -5
- package/packages/core/src/agent/providers/index.ts +39 -4
- package/packages/core/src/agent/realtime-providers/gemini-live.ts +238 -0
- package/packages/core/src/agent/realtime-providers/index.ts +29 -0
- package/packages/core/src/agent/realtime-providers/interface.ts +108 -0
- package/packages/core/src/agent/reflector.ts +38 -15
- package/packages/core/src/agent/run-store.ts +8 -8
- package/packages/core/src/agent/service.ts +10 -10
- package/packages/core/src/agent/skill-selector.ts +6 -6
- package/packages/core/src/agent/thread-id.ts +71 -0
- package/packages/core/src/agent/thread-store.ts +293 -0
- package/packages/core/src/agent/tool-selector.ts +9 -5
- package/packages/core/src/agent/tracer.ts +5 -5
- package/packages/core/src/api/createAgent.ts +67 -2
- package/packages/core/src/artifacts/index.ts +15 -0
- package/packages/core/src/artifacts/store.ts +161 -5
- package/packages/core/src/canvas/emitter.ts +2 -2
- package/packages/core/src/canvas/index.ts +9 -0
- package/packages/core/src/channels/telegram.ts +1 -1
- package/packages/core/src/channels/webchat.ts +1 -1
- package/packages/core/src/config/loader.ts +14 -5
- package/packages/core/src/ethics/EthicsGuard.ts +7 -1
- package/packages/core/src/events/agent-bus.ts +3 -3
- package/packages/core/src/events/channel-narration.ts +3 -3
- package/packages/core/src/events/event-bus.ts +1 -1
- package/packages/core/src/events/index.ts +18 -0
- package/packages/core/src/events/narration.ts +3 -3
- package/packages/core/src/events/tool-narration.ts +4 -0
- package/packages/core/src/gateway/channel-notify.ts +103 -6
- package/packages/core/src/gateway/delegation-groups.ts +4 -4
- package/packages/core/src/gateway/durable-queue.ts +18 -6
- package/packages/core/src/gateway/index.ts +3 -0
- package/packages/core/src/gateway/job-store.ts +11 -5
- package/packages/core/src/gateway/notification-inbox.ts +2 -2
- package/packages/core/src/gateway/server.ts +2 -2
- package/packages/core/src/harness/executors.ts +493 -0
- package/packages/core/src/harness/index.ts +12 -2
- package/packages/core/src/hooks/index.ts +203 -0
- package/packages/core/src/images/index.ts +161 -0
- package/packages/core/src/index.ts +1 -0
- package/packages/core/src/mcp/MCPClient.ts +3 -3
- package/packages/core/src/mcp/hot-reload.ts +5 -5
- package/packages/core/src/mcp/tool-sync.ts +5 -5
- package/packages/core/src/mcp/transports/index.ts +2 -2
- package/packages/core/src/mcp/transports/sse.ts +1 -1
- package/packages/core/src/models/index.ts +36 -0
- package/packages/core/src/multimodal/index.ts +2 -2
- package/packages/core/src/multimodal/vision-service.ts +51 -19
- package/packages/core/src/plugins/loader.ts +4 -1
- package/packages/core/src/resilience/circuit-breaker.ts +16 -5
- package/packages/core/src/resilience/index.ts +13 -0
- package/packages/core/src/resilience/retry.ts +1 -1
- package/packages/core/src/scheduler/CronScheduler.ts +54 -27
- package/packages/core/src/scheduler/cron/expression.ts +165 -0
- package/packages/core/src/scheduler/cron/index.ts +10 -0
- package/packages/core/src/scheduler/cron/job.ts +339 -0
- package/packages/core/src/scheduler/cron/next-run.ts +121 -0
- package/packages/core/src/scheduler/cron/zoned-time.ts +138 -0
- package/packages/core/src/scheduler/index.ts +21 -3
- package/packages/core/src/scheduler/integration.ts +25 -14
- package/packages/core/src/scheduler/types.ts +3 -18
- package/packages/core/src/services/agents.ts +268 -0
- package/packages/core/src/services/cron.ts +257 -0
- package/packages/core/src/services/endpoints.ts +289 -0
- package/packages/core/src/services/ethics.ts +107 -0
- package/packages/core/src/services/images.ts +212 -0
- package/packages/core/src/services/index.ts +112 -0
- package/packages/core/src/services/mcp.ts +201 -0
- package/packages/core/src/services/memory.ts +133 -0
- package/packages/core/src/services/models.ts +179 -0
- package/packages/core/src/services/providers.ts +152 -0
- package/packages/core/src/services/setup.ts +222 -0
- package/packages/core/src/services/skills.ts +241 -0
- package/packages/core/src/services/swarms.ts +307 -0
- package/packages/core/src/services/tools.ts +106 -0
- package/packages/core/src/sessions/index.ts +268 -0
- package/packages/core/src/sessions/resolve.ts +108 -0
- package/packages/core/src/skills/SkillLoader.ts +8 -1
- package/packages/core/src/skills/bundled/artifacts/artifact_reader/SKILL.md +105 -0
- package/packages/core/src/skills/bundled/cron_manager/SKILL.md +21 -11
- package/packages/core/src/skills/bundled/images/image_editor/SKILL.md +120 -0
- package/packages/core/src/skills/bundled/web/browser_automate/SKILL.md +12 -3
- package/packages/core/src/skills/bundled/web/browser_scrape/SKILL.md +22 -7
- package/packages/core/src/skills/bundled-data.generated.ts +110 -12
- package/packages/core/src/storage/bootstrap.ts +107 -12
- package/packages/core/src/storage/causal-events.ts +1 -1
- package/packages/core/src/storage/collections.ts +138 -2
- package/packages/core/src/storage/crypto.ts +35 -4
- package/packages/core/src/storage/hive.ts +1 -1
- package/packages/core/src/storage/hivedb.ts +10 -1
- package/packages/core/src/storage/index.ts +2 -1
- package/packages/core/src/storage/onboarding.ts +61 -45
- package/packages/core/src/storage/reconcile.ts +11 -6
- package/packages/core/src/storage/seed.ts +191 -23
- package/packages/core/src/storage/usage.ts +3 -3
- package/packages/core/src/swarm/AgentExecutor.ts +2 -2
- package/packages/core/src/swarm/Coordinator.ts +8 -8
- package/packages/core/src/swarm/EventBridge.ts +2 -2
- package/packages/core/src/swarm/RoleSwarm.ts +234 -0
- package/packages/core/src/swarm/TaskGraph.ts +2 -2
- package/packages/core/src/swarm/index.ts +7 -0
- package/packages/core/src/swarm/presets/HiveLearnPreset.ts +2 -2
- package/packages/core/src/swarm/presets/ResearchPreset.ts +2 -2
- package/packages/core/src/swarm/strategies/ParallelStrategy.ts +1 -1
- package/packages/core/src/swarm/strategies/PriorityStrategy.ts +3 -3
- package/packages/core/src/swarm/types.ts +3 -18
- package/packages/core/src/tool-runtime/embedded-worker.generated.ts +21 -0
- package/packages/core/src/tool-runtime/index.ts +129 -14
- package/packages/core/src/tools/ToolExecutor.ts +7 -3
- package/packages/core/src/tools/agents/index.ts +18 -60
- package/packages/core/src/tools/cli/index.ts +55 -0
- package/packages/core/src/tools/core/index.ts +52 -4
- package/packages/core/src/tools/cron/index.ts +8 -8
- package/packages/core/src/tools/images/index.ts +130 -0
- package/packages/core/src/tools/index.ts +14 -1
- package/packages/core/src/tools/office/office-escribir-xlsx.ts +2 -1
- package/packages/core/src/tools/office/office-leer-xlsx.ts +2 -1
- package/packages/core/src/tools/office/xlsx-loader.ts +19 -0
- package/packages/core/src/tools/web/artifact-inspect.ts +2 -2
- package/packages/core/src/tools/web/artifact-read.ts +162 -0
- package/packages/core/src/tools/web/browser-backend.ts +141 -44
- package/packages/core/src/tools/web/browser-click.ts +2 -2
- package/packages/core/src/tools/web/browser-extract.ts +2 -2
- package/packages/core/src/tools/web/browser-navigate.ts +2 -2
- package/packages/core/src/tools/web/browser-screenshot.ts +12 -5
- package/packages/core/src/tools/web/browser-script.ts +2 -2
- package/packages/core/src/tools/web/browser-service.ts +63 -384
- package/packages/core/src/tools/web/browser-session.ts +125 -0
- package/packages/core/src/tools/web/browser-type.ts +2 -2
- package/packages/core/src/tools/web/browser-wait.ts +2 -2
- package/packages/core/src/tools/web/computer-use.ts +553 -0
- package/packages/core/src/tools/web/index.ts +8 -1
- package/packages/core/src/tools/web/webview-backend.ts +460 -21
- package/packages/core/src/utils/index.ts +1 -0
- package/packages/core/src/utils/logger.ts +12 -4
- package/packages/core/src/utils/redact-binary.ts +17 -0
- package/packages/core/src/utils/toon.ts +1 -1
- package/packages/core/src/voice/index.ts +6 -6
- package/bun.lock +0 -859
- package/bunfig.toml +0 -9
- package/docs/API-AGENTS.md +0 -367
- package/docs/API-CONTEXT-COMPILER.md +0 -249
- package/docs/API-DAG-SCHEDULER.md +0 -273
- package/docs/API-TOOLS-SKILLS-CHANNELS.md +0 -446
- package/docs/API-WORKERS-EVENTS.md +0 -299
- package/docs/HIVE-HARNESS.md +0 -113
- package/docs/INDEX.md +0 -190
- package/docs/TEMPLATE-HIVE-APP.md +0 -360
- package/packages/cli/package.json +0 -17
- package/packages/cli/src/commands/create-app.test.ts +0 -180
- package/packages/core/package.json +0 -70
- package/packages/core/src/api/createAgent.test.ts +0 -160
- package/packages/core/src/canvas/canvas.test.ts +0 -36
- package/packages/core/src/channels/channels.test.ts +0 -18
- package/packages/core/src/ethics/EthicsGuard.test.ts +0 -108
- package/packages/core/src/gateway/gateway.test.ts +0 -38
- package/packages/core/src/memory/Scratchpad.test.ts +0 -68
- package/packages/core/src/scheduler/scheduler.test.ts +0 -15
- package/packages/core/src/skills/skills.test.ts +0 -62
- package/packages/core/src/swarm/swarm.test.ts +0 -24
- package/packages/core/src/tool-runtime/tool-runtime.test.ts +0 -99
- package/packages/core/src/tools/ToolRegistry.test.ts +0 -98
- package/packages/core/src/tools/api/api-request.test.ts +0 -164
- package/packages/core/src/tools/web/browser-service.test.ts +0 -83
- package/packages/core/src/workers/workers.test.ts +0 -41
- package/scripts/bump-version.ts +0 -248
- package/scripts/generate-skill-bundle.ts +0 -108
- package/test/acceptance-checks.test.ts +0 -403
- package/test/agent-loop-terminal-synthesis.test.ts +0 -32
- package/test/browser-backend.test.ts +0 -308
- package/test/catalog-agents-stay-enabled.test.ts +0 -117
- package/test/causal-events.test.ts +0 -117
- package/test/compaction.test.ts +0 -105
- package/test/context-compiler.test.ts +0 -269
- package/test/curator.test.ts +0 -130
- package/test/durable-queue.test.ts +0 -114
- package/test/harness-barrel.test.ts +0 -64
- package/test/hive-helpers.test.ts +0 -130
- package/test/hivedb-search.test.ts +0 -189
- package/test/internal-turns.test.ts +0 -166
- package/test/job-idempotency.test.ts +0 -68
- package/test/job-retry-backoff.test.ts +0 -184
- package/test/job-store.test.ts +0 -381
- package/test/llm-retry.test.ts +0 -97
- package/test/memory-perf.test.ts +0 -774
- package/test/minimal-loadout.test.ts +0 -78
- package/test/model-catalog.test.ts +0 -105
- package/test/preload.ts +0 -12
- package/test/reflector.test.ts +0 -320
- package/test/retention-cap.test.ts +0 -91
- package/test/retired-capabilities-pruned.test.ts +0 -192
- package/test/run-store.test.ts +0 -355
- package/test/scratchpad.test.ts +0 -74
- package/test/secrets-durability.test.ts +0 -119
- package/test/seed-model-reseed.test.ts +0 -155
- package/test/setup-agent-seed.test.ts +0 -264
- package/test/tool-inventory.test.ts +0 -65
- package/test/tool-runtime.test.ts +0 -258
- package/test/tool-selector-runtime-tools.test.ts +0 -117
- package/test/toon.test.ts +0 -429
- package/tsconfig.json +0 -42
|
@@ -1,364 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* BrowserService —
|
|
2
|
+
* BrowserService — el navegador que usan las tools de `tools/web/`.
|
|
3
3
|
*
|
|
4
|
-
* `
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Hay un solo backend: `WebViewBackend` (webview-backend.ts), que es
|
|
5
|
+
* `Bun.WebView` in-process. No se instala nada ni se descarga nada; lo único
|
|
6
|
+
* que pide es un navegador Chromium en el sistema (o `BUN_CHROME_PATH`), y con
|
|
7
|
+
* ese motor corre headless, así que sirve igual en un escritorio que en un
|
|
8
|
+
* servidor sin display.
|
|
7
9
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
10
|
+
* Acá vivió un segundo backend por CLI (agent-browser) mientras se creyó que el
|
|
11
|
+
* WebView necesitaba entorno gráfico. Medido en Bun 1.4 no era cierto, y lo que
|
|
12
|
+
* quedaba era el costo: ~40 ms de `Bun.spawn` por operación contra ~0,3 ms, un
|
|
13
|
+
* `bun add agent-browser@latest` ejecutado en producción al primer uso —versión
|
|
14
|
+
* flotante, bajada de npm— y ~88 MB más su propia copia de Chrome. Se retiró.
|
|
11
15
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* 3. El daemon de agent-browser maneja Chrome internamente via CDP.
|
|
16
|
+
* El servicio es un singleton con una sola vista: `getView()` la abre al primer
|
|
17
|
+
* uso y la reutiliza, para que `browser_navigate` establezca el contexto sobre
|
|
18
|
+
* el que operan las tools siguientes.
|
|
16
19
|
*/
|
|
17
20
|
|
|
18
21
|
import { logger } from "../../utils/logger.ts";
|
|
19
22
|
import type { Config } from "../../config/loader.ts";
|
|
20
|
-
import {
|
|
21
|
-
import { homedir, tmpdir } from "os";
|
|
22
|
-
import { dirname, join, resolve } from "path";
|
|
23
|
-
import {
|
|
24
|
-
resolveBackendKind,
|
|
25
|
-
type BrowserBackend,
|
|
26
|
-
type BrowserBackendKind,
|
|
27
|
-
type ScreenshotOptions,
|
|
28
|
-
type SnapshotOptions,
|
|
29
|
-
} from "./browser-backend.ts";
|
|
23
|
+
import { resolveBackendKind, type BrowserBackend, type BrowserBackendKind } from "./browser-backend.ts";
|
|
30
24
|
|
|
31
25
|
const log = logger.child("browser-service");
|
|
32
26
|
|
|
33
|
-
// ─── Instalación lazy de agent-browser ────────────────────────────────────────
|
|
34
|
-
|
|
35
|
-
const HIVE_DIR = join(homedir(), ".hive");
|
|
36
|
-
const AGENT_BROWSER_DIR = join(HIVE_DIR, "agent-browser");
|
|
37
|
-
const AGENT_BROWSER_PKG_JSON = join(AGENT_BROWSER_DIR, "package.json");
|
|
38
|
-
const DEFAULT_SESSION_NAME = "hive";
|
|
39
|
-
|
|
40
|
-
/** Check if agent-browser is installed in the cache dir by running --version */
|
|
41
|
-
async function isAgentBrowserInstalled(): Promise<boolean> {
|
|
42
|
-
if (!existsSync(AGENT_BROWSER_PKG_JSON)) return false;
|
|
43
|
-
try {
|
|
44
|
-
const proc = Bun.spawn(["bun", "run", "agent-browser", "--version"], {
|
|
45
|
-
cwd: AGENT_BROWSER_DIR,
|
|
46
|
-
stdout: "pipe",
|
|
47
|
-
stderr: "pipe",
|
|
48
|
-
});
|
|
49
|
-
const exitCode = await proc.exited;
|
|
50
|
-
return exitCode === 0;
|
|
51
|
-
} catch {
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async function installAgentBrowser(): Promise<void> {
|
|
57
|
-
mkdirSync(AGENT_BROWSER_DIR, { recursive: true });
|
|
58
|
-
|
|
59
|
-
// Create minimal package.json
|
|
60
|
-
const pkg = { name: "hive-agent-browser", version: "1.0.0", dependencies: {} };
|
|
61
|
-
await Bun.write(AGENT_BROWSER_PKG_JSON, JSON.stringify(pkg, null, 2));
|
|
62
|
-
|
|
63
|
-
log.info("📦 Instalando agent-browser (primera vez, ~75MB)...");
|
|
64
|
-
const proc = Bun.spawn(["bun", "add", "agent-browser@latest"], {
|
|
65
|
-
cwd: AGENT_BROWSER_DIR,
|
|
66
|
-
stdout: "pipe",
|
|
67
|
-
stderr: "pipe",
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
const exitCode = await proc.exited;
|
|
71
|
-
const stderr = await new Response(proc.stderr).text();
|
|
72
|
-
|
|
73
|
-
if (exitCode !== 0) {
|
|
74
|
-
throw new Error(`bun add agent-browser failed: ${stderr}`);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
log.info("✅ agent-browser instalado.");
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/** Run agent-browser CLI from the cache directory — cross-platform via bun run */
|
|
81
|
-
async function runAgentBrowser(
|
|
82
|
-
args: string[]
|
|
83
|
-
): Promise<{ success: boolean; data?: any; error?: string }> {
|
|
84
|
-
const proc = Bun.spawn(["bun", "run", "agent-browser", ...args], {
|
|
85
|
-
cwd: AGENT_BROWSER_DIR,
|
|
86
|
-
stdout: "pipe",
|
|
87
|
-
stderr: "pipe",
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
const stdout = await new Response(proc.stdout).text();
|
|
91
|
-
const stderr = await new Response(proc.stderr).text();
|
|
92
|
-
const exitCode = await proc.exited;
|
|
93
|
-
|
|
94
|
-
if (exitCode !== 0 && !stdout.trim()) {
|
|
95
|
-
throw new Error(stderr || `agent-browser ${args[0]} failed`);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
try {
|
|
99
|
-
const result = JSON.parse(stdout.trim().split("\n").pop() || "{}");
|
|
100
|
-
return result;
|
|
101
|
-
} catch {
|
|
102
|
-
return { success: true, data: { raw: stdout.trim() } };
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
async function ensureChromeInstalled(): Promise<void> {
|
|
107
|
-
log.info("🔍 Verificando Chrome para agent-browser...");
|
|
108
|
-
const res = await runAgentBrowser(["open", "about:blank", "--session", DEFAULT_SESSION_NAME, "--json"]);
|
|
109
|
-
|
|
110
|
-
if (!res.success) {
|
|
111
|
-
const err = res.error || "";
|
|
112
|
-
// Chrome not installed — trigger install
|
|
113
|
-
if (err.includes("not found") || err.includes("install")) {
|
|
114
|
-
log.info("📥 Descargando Chrome (agent-browser install)...");
|
|
115
|
-
const installProc = Bun.spawn(["bun", "run", "agent-browser", "install"], {
|
|
116
|
-
cwd: AGENT_BROWSER_DIR,
|
|
117
|
-
stdout: "pipe",
|
|
118
|
-
stderr: "pipe",
|
|
119
|
-
});
|
|
120
|
-
const installExit = await installProc.exited;
|
|
121
|
-
if (installExit !== 0) {
|
|
122
|
-
const installErr = await new Response(installProc.stderr).text();
|
|
123
|
-
throw new Error(`agent-browser install failed: ${installErr}`);
|
|
124
|
-
}
|
|
125
|
-
log.info("✅ Chrome descargado.");
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
throw new Error(`agent-browser chrome check failed: ${err}`);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// ─── AgentBrowserView (API compatible con CDPClient) ──────────────────────────
|
|
133
|
-
|
|
134
|
-
export class AgentBrowserView implements BrowserBackend {
|
|
135
|
-
private sessionName: string;
|
|
136
|
-
private _url = "";
|
|
137
|
-
|
|
138
|
-
get url(): string { return this._url; }
|
|
139
|
-
get title(): string { return ""; }
|
|
140
|
-
get loading(): boolean { return false; }
|
|
141
|
-
get isConnected(): boolean { return true; }
|
|
142
|
-
|
|
143
|
-
constructor(sessionName: string = DEFAULT_SESSION_NAME) {
|
|
144
|
-
this.sessionName = sessionName;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
protected async run(args: string[]): Promise<{ success: boolean; data?: any; error?: string }> {
|
|
148
|
-
return runAgentBrowser(["--session", this.sessionName, "--json", ...args]);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
async navigate(url: string): Promise<void> {
|
|
152
|
-
// Ensure protocol
|
|
153
|
-
const target = /^https?:\/\//.test(url) ? url : `https://${url}`;
|
|
154
|
-
const res = await this.run(["open", target]);
|
|
155
|
-
if (!res.success) throw new Error(res.error || "navigate failed");
|
|
156
|
-
this._url = res.data?.url || target;
|
|
157
|
-
// Small delay to let JS settle (same as old implementation)
|
|
158
|
-
await new Promise(r => setTimeout(r, 500));
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
async evaluate<T = unknown>(script: string): Promise<T> {
|
|
162
|
-
let wrapped = script;
|
|
163
|
-
const trimmed = script.trim();
|
|
164
|
-
|
|
165
|
-
// If script contains top-level await, wrap in async IIFE to make it valid JS
|
|
166
|
-
if (/\bawait\b/.test(script) && !trimmed.startsWith("(async") && !trimmed.startsWith("async function")) {
|
|
167
|
-
if (trimmed.startsWith("return")) {
|
|
168
|
-
wrapped = `(async () => { ${script} })()`;
|
|
169
|
-
} else {
|
|
170
|
-
wrapped = `(async () => { return ${script}; })()`;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const res = await this.run(["eval", wrapped]);
|
|
175
|
-
if (!res.success) throw new Error(res.error || "eval failed");
|
|
176
|
-
return res.data?.result as T;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
async screenshot(options?: ScreenshotOptions): Promise<string> {
|
|
180
|
-
// Build args
|
|
181
|
-
const args: string[] = ["screenshot"];
|
|
182
|
-
|
|
183
|
-
if (options?.format === "jpeg") {
|
|
184
|
-
args.push("--screenshot-format", "jpeg");
|
|
185
|
-
}
|
|
186
|
-
if (options?.quality) {
|
|
187
|
-
args.push("--screenshot-quality", String(options.quality));
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// If clip/selector is provided, agent-browser screenshot accepts a positional selector
|
|
191
|
-
// For element screenshots, we can pass a selector as first positional arg
|
|
192
|
-
// But we don't have selector in options here — the old CDPClient didn't use it either
|
|
193
|
-
// screenshotElement helper handles element-specific screenshots
|
|
194
|
-
|
|
195
|
-
const res = await this.run(args);
|
|
196
|
-
if (!res.success) throw new Error(res.error || "screenshot failed");
|
|
197
|
-
|
|
198
|
-
const path = res.data?.path as string;
|
|
199
|
-
if (!path) throw new Error("screenshot did not return a path");
|
|
200
|
-
|
|
201
|
-
const data = readFileSync(path);
|
|
202
|
-
const base64 = Buffer.from(data).toString("base64");
|
|
203
|
-
|
|
204
|
-
// Cleanup temp file
|
|
205
|
-
try { rmSync(path); } catch { /* ignore */ }
|
|
206
|
-
|
|
207
|
-
return base64;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
async click(selector: string, _options?: Record<string, unknown>): Promise<void> {
|
|
211
|
-
const res = await this.run(["click", selector]);
|
|
212
|
-
if (!res.success) throw new Error(res.error || `click failed: ${selector}`);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
async type(text: string): Promise<void> {
|
|
216
|
-
// Fallback: keyboard inserttext (requires focused element)
|
|
217
|
-
const res = await this.run(["keyboard", "inserttext", text]);
|
|
218
|
-
if (!res.success) throw new Error(res.error || "type failed");
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
async typeIn(selector: string, text: string): Promise<void> {
|
|
222
|
-
const res = await this.run(["type", selector, text]);
|
|
223
|
-
if (!res.success) throw new Error(res.error || `type failed: ${selector}`);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
async fill(selector: string, text: string): Promise<void> {
|
|
227
|
-
const res = await this.run(["fill", selector, text]);
|
|
228
|
-
if (!res.success) throw new Error(res.error || `fill failed: ${selector}`);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
async press(key: string, options?: { modifiers?: string[] }): Promise<void> {
|
|
232
|
-
const modifiers = options?.modifiers ?? [];
|
|
233
|
-
const combo = modifiers.length > 0
|
|
234
|
-
? `${modifiers.join("+")}+${key}`
|
|
235
|
-
: key;
|
|
236
|
-
const res = await this.run(["press", combo]);
|
|
237
|
-
if (!res.success) throw new Error(res.error || `press failed: ${combo}`);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
async scroll(dx: number, dy: number): Promise<void> {
|
|
241
|
-
const dir = dy > 0 ? "down" : dy < 0 ? "up" : dx > 0 ? "right" : "left";
|
|
242
|
-
const px = Math.abs(dy || dx);
|
|
243
|
-
const res = await this.run(["scroll", dir, String(px)]);
|
|
244
|
-
if (!res.success) throw new Error(res.error || "scroll failed");
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
async scrollTo(selector: string, _options?: { behavior?: "smooth" | "instant" }): Promise<void> {
|
|
248
|
-
// agent-browser has scrollintoview (behavior not supported via CLI)
|
|
249
|
-
const res = await this.run(["scrollintoview", selector]);
|
|
250
|
-
if (!res.success) throw new Error(res.error || `scrollTo failed: ${selector}`);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
async back(): Promise<void> {
|
|
254
|
-
const res = await this.run(["back"]);
|
|
255
|
-
if (!res.success) throw new Error(res.error || "back failed");
|
|
256
|
-
await new Promise<void>(r => setTimeout(r, 800));
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
async forward(): Promise<void> {
|
|
260
|
-
const res = await this.run(["forward"]);
|
|
261
|
-
if (!res.success) throw new Error(res.error || "forward failed");
|
|
262
|
-
await new Promise<void>(r => setTimeout(r, 800));
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
async reload(): Promise<void> {
|
|
266
|
-
const res = await this.run(["reload"]);
|
|
267
|
-
if (!res.success) throw new Error(res.error || "reload failed");
|
|
268
|
-
await new Promise<void>(r => setTimeout(r, 1000));
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
async resize(width: number, height: number): Promise<void> {
|
|
272
|
-
const res = await this.run(["set", "viewport", String(width), String(height)]);
|
|
273
|
-
if (!res.success) throw new Error(res.error || "resize failed");
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/** Screenshot recortado a un elemento — agent-browser acepta el selector posicional. */
|
|
277
|
-
async screenshotElement(selector: string): Promise<string> {
|
|
278
|
-
const res = await this.run(["screenshot", selector]);
|
|
279
|
-
if (!res.success) throw new Error(res.error || `screenshot failed: ${selector}`);
|
|
280
|
-
|
|
281
|
-
const path = res.data?.path as string;
|
|
282
|
-
if (!path) throw new Error("screenshot did not return a path");
|
|
283
|
-
|
|
284
|
-
const base64 = Buffer.from(readFileSync(path)).toString("base64");
|
|
285
|
-
try { rmSync(path); } catch { /* ignore */ }
|
|
286
|
-
return base64;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/** Capture accessibility tree snapshot (compact, AI-optimized). ~200-600 chars vs ~3000+ innerText. */
|
|
290
|
-
async snapshot(options?: SnapshotOptions): Promise<string> {
|
|
291
|
-
const args = ["snapshot"];
|
|
292
|
-
if (options?.compact !== false) args.push("-c");
|
|
293
|
-
if (options?.depth) args.push("-d", String(options.depth));
|
|
294
|
-
if (options?.interactiveOnly) args.push("-i");
|
|
295
|
-
|
|
296
|
-
const res = await this.run(args);
|
|
297
|
-
if (!res.success) throw new Error(res.error || "snapshot failed");
|
|
298
|
-
return res.data?.snapshot as string || "";
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
async cdp<T = unknown>(method: string, params?: Record<string, unknown>): Promise<T> {
|
|
302
|
-
const script = `
|
|
303
|
-
(() => {
|
|
304
|
-
// agent-browser does not expose raw CDP directly via CLI for all methods.
|
|
305
|
-
// For common methods we can emulate; for others we return a notice.
|
|
306
|
-
const method = ${JSON.stringify(method)};
|
|
307
|
-
const params = ${JSON.stringify(params ?? {})};
|
|
308
|
-
return { method, params, note: "CDP passthrough not fully supported by agent-browser CLI" };
|
|
309
|
-
})()
|
|
310
|
-
`;
|
|
311
|
-
const res = await this.run(["eval", script]);
|
|
312
|
-
if (!res.success) throw new Error(res.error || `cdp failed: ${method}`);
|
|
313
|
-
return res.data?.result as T;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
close(): void {
|
|
317
|
-
// Close the session
|
|
318
|
-
this.run(["close"]).catch(() => { /* ignore */ });
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// ─── Backwards compatibility exports ──────────────────────────────────────────
|
|
323
|
-
|
|
324
|
-
/** @deprecated Use AgentBrowserView instead */
|
|
325
|
-
export class CDPClient extends AgentBrowserView {
|
|
326
|
-
private _launched = false;
|
|
327
|
-
|
|
328
|
-
async launch(_spec?: unknown, _options?: unknown): Promise<void> {
|
|
329
|
-
if (this._launched) return;
|
|
330
|
-
// Verify agent-browser is working by opening about:blank
|
|
331
|
-
const res = await this.run(["open", "about:blank"]);
|
|
332
|
-
if (!res.success) throw new Error(res.error || "Failed to launch agent-browser");
|
|
333
|
-
this._launched = true;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
static closeAll(): void {
|
|
337
|
-
// agent-browser sessions are managed by the daemon; no explicit cleanup needed
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/** @deprecated No longer used — agent-browser handles browser detection internally */
|
|
342
|
-
export function detectBrowser(_options?: unknown): undefined {
|
|
343
|
-
return undefined;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
/** @deprecated No longer used */
|
|
347
|
-
export type LaunchSpec = { kind: "remote"; cdpUrl: string };
|
|
348
|
-
|
|
349
|
-
// ─── BrowserService (singleton) ───────────────────────────────────────────────
|
|
350
|
-
|
|
351
27
|
/** Alias histórico: las tools sólo dependen del contrato, no de la implementación. */
|
|
352
28
|
export type BrowserView = BrowserBackend;
|
|
353
29
|
|
|
354
30
|
/** Re-export para que quien importe el servicio no tenga que conocer el módulo del contrato. */
|
|
355
31
|
export type { BrowserBackend, BrowserBackendKind } from "./browser-backend.ts";
|
|
356
|
-
export { isWebViewSupported, resolveBackendKind } from "./browser-backend.ts";
|
|
32
|
+
export { isWebViewSupported, resolveBackendKind, findChrome } from "./browser-backend.ts";
|
|
357
33
|
|
|
358
34
|
let _client: BrowserBackend | null = null;
|
|
359
35
|
let _available = false;
|
|
360
36
|
let _launching = false;
|
|
361
|
-
let _kind: BrowserBackendKind = "
|
|
37
|
+
let _kind: BrowserBackendKind = "webview";
|
|
362
38
|
|
|
363
39
|
export class BrowserService {
|
|
364
40
|
private static instance: BrowserService | null = null;
|
|
@@ -376,7 +52,10 @@ export class BrowserService {
|
|
|
376
52
|
}
|
|
377
53
|
|
|
378
54
|
/**
|
|
379
|
-
*
|
|
55
|
+
* Deja el servicio listo. No abre el navegador: eso pasa en el primer uso.
|
|
56
|
+
*
|
|
57
|
+
* Si el entorno no tiene motor, `ensureView()` falla ahí con un mensaje que
|
|
58
|
+
* dice qué instalar, y las tools reportan el navegador como no disponible.
|
|
380
59
|
*/
|
|
381
60
|
async start(): Promise<boolean> {
|
|
382
61
|
const b = this.config.tools?.browser;
|
|
@@ -386,39 +65,8 @@ export class BrowserService {
|
|
|
386
65
|
}
|
|
387
66
|
|
|
388
67
|
_kind = resolveBackendKind(b?.backend);
|
|
389
|
-
|
|
390
|
-
if (_kind === "webview") {
|
|
391
|
-
// No hay nada que instalar ni que descargar: el WebView se crea al primer
|
|
392
|
-
// uso. Si el entorno no lo soporta, ensureView() falla ahí y el servicio
|
|
393
|
-
// queda no disponible, igual que cuando agent-browser no arranca.
|
|
394
|
-
_available = true;
|
|
395
|
-
log.info("✅ Backend de navegador: Bun.WebView (in-process, sin Chrome)");
|
|
396
|
-
return true;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
const installed = await isAgentBrowserInstalled();
|
|
400
|
-
|
|
401
|
-
if (!installed) {
|
|
402
|
-
try {
|
|
403
|
-
await installAgentBrowser();
|
|
404
|
-
} catch (err) {
|
|
405
|
-
log.warn(`No se pudo instalar agent-browser: ${(err as Error).message}`);
|
|
406
|
-
log.warn(" Instalar manualmente: bun add -g agent-browser");
|
|
407
|
-
_available = false;
|
|
408
|
-
return false;
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
try {
|
|
413
|
-
await ensureChromeInstalled();
|
|
414
|
-
} catch (err) {
|
|
415
|
-
log.warn(`Chrome no pudo prepararse: ${(err as Error).message}`);
|
|
416
|
-
_available = false;
|
|
417
|
-
return false;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
68
|
_available = true;
|
|
421
|
-
log.info("✅
|
|
69
|
+
log.info("✅ Navegador: Bun.WebView (in-process, headless)");
|
|
422
70
|
return true;
|
|
423
71
|
}
|
|
424
72
|
|
|
@@ -431,14 +79,15 @@ export class BrowserService {
|
|
|
431
79
|
}
|
|
432
80
|
_launching = true;
|
|
433
81
|
try {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
}
|
|
441
|
-
|
|
82
|
+
const { WebViewBackend } = await import("./webview-backend.ts");
|
|
83
|
+
// Headless salvo que se pida lo contrario con `tools.browser.headless: false`.
|
|
84
|
+
const visible = this.config.tools?.browser?.headless === false;
|
|
85
|
+
_client = new WebViewBackend({
|
|
86
|
+
show: visible,
|
|
87
|
+
persistSession: this.config.tools?.browser?.persistSession,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
log.info(`✅ Browser abierto (${visible ? "con ventana" : "headless"})`);
|
|
442
91
|
return true;
|
|
443
92
|
} catch (err) {
|
|
444
93
|
log.warn(`Browser no pudo iniciarse: ${(err as Error).message}`);
|
|
@@ -483,6 +132,14 @@ export class BrowserService {
|
|
|
483
132
|
|
|
484
133
|
async stop(): Promise<void> {
|
|
485
134
|
if (_client) {
|
|
135
|
+
// El volcado de cookies está debounceado: si se cierra antes de que
|
|
136
|
+
// dispare, el login de esta sesión se pierde. Por eso se fuerza acá.
|
|
137
|
+
const flushable = _client as BrowserBackend & { flushSession?: () => Promise<void> };
|
|
138
|
+
if (typeof flushable.flushSession === "function") {
|
|
139
|
+
await flushable.flushSession().catch((err: Error) => {
|
|
140
|
+
log.warn(`no se pudo guardar la sesión al cerrar: ${err.message}`);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
486
143
|
_client.close();
|
|
487
144
|
_client = null;
|
|
488
145
|
log.info("✅ Browser cerrado");
|
|
@@ -508,6 +165,28 @@ export function getBrowserService(): BrowserService | null {
|
|
|
508
165
|
return browserServiceInstance;
|
|
509
166
|
}
|
|
510
167
|
|
|
168
|
+
/**
|
|
169
|
+
* Cierra todo lo que este proceso haya abierto, pase lo que pase.
|
|
170
|
+
*
|
|
171
|
+
* `stop()` es el camino ordenado —vuelca la sesión antes de cerrar— y esto es
|
|
172
|
+
* la red debajo: `Bun.WebView.closeAll()` mata cualquier vista que haya quedado
|
|
173
|
+
* viva, incluida alguna abierta fuera del servicio. El apagado del gateway
|
|
174
|
+
* llama a esto; antes llamaba a un `CDPClient.closeAll()` que no hacía nada, y
|
|
175
|
+
* por eso Chrome sobrevivía al gateway y la sesión no se guardaba nunca.
|
|
176
|
+
*/
|
|
177
|
+
export async function shutdownBrowser(): Promise<void> {
|
|
178
|
+
try {
|
|
179
|
+
await browserServiceInstance?.stop();
|
|
180
|
+
} catch (err) {
|
|
181
|
+
log.warn(`cierre del navegador incompleto: ${(err as Error).message}`);
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
(globalThis as { Bun?: { WebView?: { closeAll?: () => void } } }).Bun?.WebView?.closeAll?.();
|
|
185
|
+
} catch {
|
|
186
|
+
/* no hay vistas que cerrar */
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
511
190
|
// ─── Helpers (misma API que antes) ───────────────────────────────────────────
|
|
512
191
|
|
|
513
192
|
export async function waitForSelector(
|
|
@@ -542,7 +221,7 @@ export async function screenshotElement(
|
|
|
542
221
|
view: BrowserBackend,
|
|
543
222
|
selector: string
|
|
544
223
|
): Promise<string> {
|
|
545
|
-
//
|
|
546
|
-
//
|
|
224
|
+
// El recorte es responsabilidad del backend: sabe si puede pedirlo por CDP o
|
|
225
|
+
// si tiene que resolverlo sobre el viewport.
|
|
547
226
|
return view.screenshotElement(selector);
|
|
548
227
|
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistencia de la sesión del navegador — cookies entre reinicios.
|
|
3
|
+
*
|
|
4
|
+
* `Bun.WebView` abre Chrome con un perfil efímero: `/tmp/.<hash>.bun-chrome`,
|
|
5
|
+
* donde el hash cambia de un proceso a otro y no hay forma de fijarlo (probado:
|
|
6
|
+
* el constructor ignora `userDataDir` y `args`). Sin esto, cada reinicio del
|
|
7
|
+
* gateway perdería todos los logins que el agente haya hecho.
|
|
8
|
+
*
|
|
9
|
+
* La solución es guardar las cookies por CDP y volver a ponerlas al arrancar.
|
|
10
|
+
* Van al almacén de secretos —keychain del sistema, o la colección cifrada— y
|
|
11
|
+
* no a un JSON en claro: una cookie de sesión vale tanto como la contraseña.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { logger } from "../../utils/logger.ts";
|
|
15
|
+
import { loadSecret, storeSecret, deleteSecret } from "../../storage/crypto.ts";
|
|
16
|
+
|
|
17
|
+
const log = logger.child("browser-session");
|
|
18
|
+
|
|
19
|
+
const SECRET_NAME = "browser.session.cookies";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Tope de lo que se guarda. El almacén de secretos no está pensado para
|
|
23
|
+
* megabytes, y un navegador que estuvo horas de paseo junta cookies de
|
|
24
|
+
* publicidad que no le sirven a nadie.
|
|
25
|
+
*/
|
|
26
|
+
const MAX_BYTES = 256 * 1024;
|
|
27
|
+
|
|
28
|
+
/** Los campos que `Network.setCookies` acepta de vuelta. */
|
|
29
|
+
export interface StoredCookie {
|
|
30
|
+
name: string;
|
|
31
|
+
value: string;
|
|
32
|
+
domain: string;
|
|
33
|
+
path?: string;
|
|
34
|
+
expires?: number;
|
|
35
|
+
httpOnly?: boolean;
|
|
36
|
+
secure?: boolean;
|
|
37
|
+
sameSite?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* ¿Se guarda la sesión? Default sí; `HIVE_BROWSER_PERSIST_SESSION=0` o
|
|
42
|
+
* `tools.browser.persistSession: false` la apagan (kioscos, equipos
|
|
43
|
+
* compartidos, o cuando se quiere que cada tarea arranque sin historia).
|
|
44
|
+
*/
|
|
45
|
+
export function sessionPersistenceEnabled(configured?: boolean): boolean {
|
|
46
|
+
const env = process.env.HIVE_BROWSER_PERSIST_SESSION;
|
|
47
|
+
if (env !== undefined) return env !== "0" && env.toLowerCase() !== "false";
|
|
48
|
+
return configured !== false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Deja sólo los campos reutilizables y descarta lo ya vencido. */
|
|
52
|
+
export function normalizeCookies(raw: unknown): StoredCookie[] {
|
|
53
|
+
if (!Array.isArray(raw)) return [];
|
|
54
|
+
const ahora = Date.now() / 1000;
|
|
55
|
+
const out: StoredCookie[] = [];
|
|
56
|
+
|
|
57
|
+
for (const item of raw) {
|
|
58
|
+
// Lo que llega es JSON crudo: del protocolo, o de un archivo que alguien
|
|
59
|
+
// editó. Un null en la lista no puede tumbar la restauración entera.
|
|
60
|
+
if (typeof item !== "object" || item === null) continue;
|
|
61
|
+
const c = item as Record<string, unknown>;
|
|
62
|
+
const name = typeof c.name === "string" ? c.name : null;
|
|
63
|
+
const value = typeof c.value === "string" ? c.value : null;
|
|
64
|
+
const domain = typeof c.domain === "string" ? c.domain : null;
|
|
65
|
+
if (!name || value === null || !domain) continue;
|
|
66
|
+
|
|
67
|
+
// -1 es la marca de CDP para "cookie de sesión": esas se conservan, que son
|
|
68
|
+
// justamente las del login. Las que traen fecha y ya pasó, no.
|
|
69
|
+
const expires = typeof c.expires === "number" ? c.expires : undefined;
|
|
70
|
+
if (expires !== undefined && expires > 0 && expires < ahora) continue;
|
|
71
|
+
|
|
72
|
+
out.push({
|
|
73
|
+
name,
|
|
74
|
+
value,
|
|
75
|
+
domain,
|
|
76
|
+
path: typeof c.path === "string" ? c.path : undefined,
|
|
77
|
+
expires: expires !== undefined && expires > 0 ? expires : undefined,
|
|
78
|
+
httpOnly: c.httpOnly === true,
|
|
79
|
+
secure: c.secure === true,
|
|
80
|
+
sameSite: typeof c.sameSite === "string" ? c.sameSite : undefined,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function loadStoredCookies(): Promise<StoredCookie[]> {
|
|
87
|
+
try {
|
|
88
|
+
const raw = await loadSecret(SECRET_NAME);
|
|
89
|
+
if (!raw) return [];
|
|
90
|
+
return normalizeCookies(JSON.parse(raw));
|
|
91
|
+
} catch (err) {
|
|
92
|
+
log.warn(`no se pudo leer la sesión guardada: ${(err as Error).message}`);
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function storeCookies(cookies: unknown[]): Promise<number> {
|
|
98
|
+
const limpias = normalizeCookies(cookies);
|
|
99
|
+
if (!limpias.length) return 0;
|
|
100
|
+
|
|
101
|
+
// Si no entra, se recorta por el final: las de sesión y las de dominio propio
|
|
102
|
+
// suelen venir primero, y perder una cookie de tracking no le duele a nadie.
|
|
103
|
+
let payload = JSON.stringify(limpias);
|
|
104
|
+
let guardadas = limpias;
|
|
105
|
+
while (payload.length > MAX_BYTES && guardadas.length > 1) {
|
|
106
|
+
guardadas = guardadas.slice(0, Math.floor(guardadas.length / 2));
|
|
107
|
+
payload = JSON.stringify(guardadas);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
await storeSecret(SECRET_NAME, payload);
|
|
112
|
+
return guardadas.length;
|
|
113
|
+
} catch (err) {
|
|
114
|
+
log.warn(`no se pudo guardar la sesión: ${(err as Error).message}`);
|
|
115
|
+
return 0;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export async function clearStoredSession(): Promise<void> {
|
|
120
|
+
try {
|
|
121
|
+
await deleteSecret(SECRET_NAME);
|
|
122
|
+
} catch (err) {
|
|
123
|
+
log.warn(`no se pudo borrar la sesión: ${(err as Error).message}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -53,7 +53,7 @@ export const browserTypeTool: Tool = {
|
|
|
53
53
|
log.warn("Browser not available");
|
|
54
54
|
return {
|
|
55
55
|
ok: false,
|
|
56
|
-
error: "Browser automation not available. Install
|
|
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 browserTypeTool: 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
|
|
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);
|
|
@@ -54,7 +54,7 @@ export const browserWaitTool: Tool = {
|
|
|
54
54
|
log.warn("Browser not available");
|
|
55
55
|
return {
|
|
56
56
|
ok: false,
|
|
57
|
-
error: "Browser automation not available. Install
|
|
57
|
+
error: "Browser automation not available. Install Chrome or Chromium (or run `hive doctor`).",
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
60
|
|
|
@@ -69,7 +69,7 @@ export const browserWaitTool: Tool = {
|
|
|
69
69
|
|
|
70
70
|
try {
|
|
71
71
|
const view = await browserService.getView();
|
|
72
|
-
if (!view) return { ok: false, error: "Browser automation not available. Install
|
|
72
|
+
if (!view) return { ok: false, error: "Browser automation not available. Install Chrome or Chromium (or run `hive doctor`)." };
|
|
73
73
|
|
|
74
74
|
if (url) {
|
|
75
75
|
await view.navigate(url);
|