alvin-bot 4.15.1 β 4.16.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 +81 -18
- package/README.md +13 -13
- package/bin/cli.js +124 -0
- package/dist/handlers/platform-message.js +2 -2
- package/dist/paths.js +12 -2
- package/dist/providers/claude-sdk-provider.js +6 -0
- package/dist/services/alvin-mcp-tools.js +1 -1
- package/dist/services/asset-index.js +5 -11
- package/dist/services/browser-manager.js +19 -6
- package/dist/services/cdp-bootstrap.js +351 -0
- package/dist/services/heartbeat.js +85 -2
- package/dist/services/memory-layers.js +1 -1
- package/dist/services/personality.js +1 -1
- package/dist/services/session.js +1 -1
- package/dist/services/skills.js +4 -7
- package/dist/services/workspaces.js +4 -4
- package/docs/security.md +4 -4
- package/package.json +1 -1
- package/skills/browse/SKILL.md +77 -70
- package/skills/social-fetch/SKILL.md +3 -3
- package/skills/webcheck/SKILL.md +1 -1
- package/test/async-agent-chunk-flow.test.ts +1 -1
- package/test/claude-sdk-tool-use-id.test.ts +1 -1
- package/test/memory-extractor.test.ts +10 -10
- package/test/memory-layers.test.ts +15 -15
- package/test/memory-sdk-injection.test.ts +4 -4
- package/test/memory-stress-restart.test.ts +2 -2
- package/test/multi-session-stress.test.ts +21 -21
- package/test/platform-session-key.test.ts +2 -2
- package/test/slack-test-connection.test.ts +3 -3
- package/test/subagent-delivery-platform-routing.test.ts +2 -2
- package/test/telegram-workspace-command.test.ts +5 -5
- package/test/workspaces.test.ts +32 -32
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,69 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Alvin Bot are documented here.
|
|
4
4
|
|
|
5
|
+
## [4.16.0] β 2026-04-20
|
|
6
|
+
|
|
7
|
+
### π Feature: bot-owned CDP Chromium β no more hub dependency
|
|
8
|
+
|
|
9
|
+
**Problem for new users:** The bot's CDP strategy and the `browse` / `social-fetch` skills referenced `~/.claude/hub/SCRIPTS/browser.sh` β a private tooling setup that only the maintainer has. New npm installs silently lacked a working CDP path; the skill-documented commands errored with "file not found". A second failure mode: when a user followed any online guide to start Chrome with `--remote-debugging-port` while their daily Chrome was already running, macOS LaunchServices silently routed the call to the existing instance without applying the flag (log: "Wird in einer aktuellen Browsersitzung geΓΆffnet"), and no CDP endpoint came up.
|
|
10
|
+
|
|
11
|
+
**Fix β three additions:**
|
|
12
|
+
|
|
13
|
+
1. **`src/services/cdp-bootstrap.ts` (new):** Spawns Playwright's bundled *Google Chrome for Testing* binary with a distinct bundle ID β zero conflict with the user's daily Chrome. Dynamic binary resolution walks the latest `chromium-NNNN/` cache directory; cross-platform (macOS arm64/x64, Linux, Windows). Idempotent `ensureRunning()` β safe to call from multiple concurrent code paths, serialized via a single-flight lock. Cleans stale PID files, verifies liveness via both process signal and CDP `/json/version` probe, captures Chromium stderr to `~/.alvin-bot/browser/chrome-cdp.log` for diagnosis.
|
|
14
|
+
|
|
15
|
+
2. **`alvin-bot browser` CLI subcommand (new):** Stable shell interface that works on every install β `start`, `stop`, `status`, `goto`, `shot`, `eval`, `tabs`, `doctor`. Wraps the bootstrap so agents in skills have a single, documented command. Screenshots default to `~/.alvin-bot/browser/screenshots/`.
|
|
16
|
+
|
|
17
|
+
3. **`browser-manager` rewired:** The `cdp` strategy now calls `cdp-bootstrap.ensureRunning()` first (works for every install), and only falls back to the hub script if present (maintainer-only dev convenience). The whole cascade still works with no hub at all.
|
|
18
|
+
|
|
19
|
+
**Skills updated:**
|
|
20
|
+
- `skills/browse/SKILL.md` β rewritten to use `alvin-bot browser ...` commands; hub-script references removed (kept as "if present" note for dev environments).
|
|
21
|
+
- `skills/social-fetch/SKILL.md` β CDP fallback line uses `alvin-bot browser goto/shot`.
|
|
22
|
+
|
|
23
|
+
**Docs:**
|
|
24
|
+
- `CLAUDE.md` β browser automation section switched to `alvin-bot browser` everywhere. Tier 0 (curl/WebFetch) now explicit as the cheapest path. Tier 1 example uses inline `node -e` + Playwright (no hub dependency).
|
|
25
|
+
- `src/paths.ts` β `HUB_BROWSER_SH` annotated as dev-only optional. New paths: `CDP_PROFILE_DIR`, `CDP_SCREENSHOTS_DIR`, `CDP_PID_FILE`, `CDP_LOG_FILE` under `~/.alvin-bot/browser/`.
|
|
26
|
+
|
|
27
|
+
**First-run setup (one-time):**
|
|
28
|
+
```bash
|
|
29
|
+
npx playwright install chromium
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Verified on 2026-04-20 with user's daily Chrome running:**
|
|
33
|
+
- `alvin-bot browser start` β PID + endpoint, no LaunchServices hijack
|
|
34
|
+
- `alvin-bot browser stop` + immediate `alvin-bot browser shot <url>` β CDP auto-starts, screenshot written (15 KB PNG in `~/.alvin-bot/browser/screenshots/`)
|
|
35
|
+
- `alvin-bot browser doctor` β all 4 checks green (binary, endpoint, PID, profile lock)
|
|
36
|
+
- `npm test` β 504/504 tests passing
|
|
37
|
+
|
|
38
|
+
## [4.15.2] β 2026-04-17
|
|
39
|
+
|
|
40
|
+
### π Fix: sleep-aware heartbeat prevents false failover after macOS wake
|
|
41
|
+
|
|
42
|
+
**Problem:** When the Mac goes to sleep, Node.js' `setInterval` pauses completely. After waking up, the first heartbeat probe runs against a CLI + network stack that's still warming up (OAuth token refresh, DNS cache cold, TCP connections stale). The 5s `isAvailable()` timeout is too tight for post-wake latency β probe fails β 2 consecutive failures (the heartbeat fires its backlog) β auto-failover to Ollama β the bot silently answers via Gemma4 instead of Claude, sometimes for hours.
|
|
43
|
+
|
|
44
|
+
**Evidence:** Logs showed a 7-hour gap (02:02β09:14 UTC) with zero heartbeat activity β the Mac was asleep. Immediately after wake, `claude-sdk: failure 1/2` β `unhealthy` β Ollama boot. The auto-recovery logic was correct but had no chance to fire before a manual restart.
|
|
45
|
+
|
|
46
|
+
**Fix β three mechanisms in `heartbeat.ts`:**
|
|
47
|
+
|
|
48
|
+
1. **Sleep detection via wall-clock drift:** If `now - lastHeartbeatRanAt > 2Γ interval`, the machine was suspended. On detection:
|
|
49
|
+
- 60s grace period where probe failures don't count toward the fail threshold
|
|
50
|
+
- All stale failure counters reset to zero (pre-sleep failures are meaningless)
|
|
51
|
+
- `isAvailable()` caches invalidated (a 7-hour-old "available: false" cache must not survive wake)
|
|
52
|
+
|
|
53
|
+
2. **Quick recovery probe:** After every failover, schedule an extra heartbeat after 60s (not 5 min). If the primary is already back, recovery happens in β€60s instead of up to 5 minutes.
|
|
54
|
+
|
|
55
|
+
3. **Cache invalidation API:** `ClaudeSDKProvider.invalidateAvailabilityCache()` exposed so the heartbeat can clear stale results after sleep.
|
|
56
|
+
|
|
57
|
+
**Typical post-sleep flow with fix:**
|
|
58
|
+
```
|
|
59
|
+
[wake] β π π΄ Sleep detected (~420min gap). Grace period 60s
|
|
60
|
+
β reset claude-sdk to healthy, invalidate caches
|
|
61
|
+
[+0s] β π π΄ claude-sdk: probe failed during grace period β not counting
|
|
62
|
+
[+60s] β grace expired β normal probe β claude-sdk healthy β
|
|
63
|
+
```
|
|
64
|
+
Without the fix, the same scenario triggered failover at +0s.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
5
68
|
## [4.15.1] β 2026-04-16
|
|
6
69
|
|
|
7
70
|
### π Patch: suppress `fallbackModel` when primary is Haiku
|
|
@@ -94,7 +157,7 @@ Four hardcoded Claude model IDs replaced with current strings: `claude-sonnet-4-
|
|
|
94
157
|
|
|
95
158
|
### π Patch: watcher zombie-entry fix (missing outputFile > 10 min = failed)
|
|
96
159
|
|
|
97
|
-
**Edge case
|
|
160
|
+
**Edge case the maintainer caught today:** a pending async-agent entry stuck in `/subagents list` for 3+ hours showing "running" β but the underlying `alvin_dispatch_agent` subprocess had already died (its output file was gone). The entry would have continued haunting the list until the 12-hour `giveUpAt` ceiling fired.
|
|
98
161
|
|
|
99
162
|
**Root cause:** `async-agent-watcher`'s `pollOnce` handled four states from `parseOutputFileStatus` β `completed` / `failed` / `running` / `missing`. For `missing` (file doesn't exist or is empty), the watcher just kept polling forever, on the assumption that a slow subprocess might eventually write. If the subprocess crashed before writing ANY output, the file never appeared, and we polled for 12 hours before timing out.
|
|
100
163
|
|
|
@@ -137,7 +200,7 @@ Four hardcoded Claude model IDs replaced with current strings: `claude-sonnet-4-
|
|
|
137
200
|
|
|
138
201
|
### π Patch: `/subagents list` now shows v4.13+ dispatch agents too
|
|
139
202
|
|
|
140
|
-
**Bug
|
|
203
|
+
**Bug the maintainer caught:** typing `/subagents list` in Telegram while a `alvin_dispatch_agent` sub-agent was actively running returned "no agents running" β even though the user could see the agent finish and deliver a result shortly after. Cross-platform effect too: `/alvin` slash command on Slack had the same display gap.
|
|
141
204
|
|
|
142
205
|
**Root cause:** two separate registries for sub-agents:
|
|
143
206
|
- `src/services/subagents.ts` `activeAgents` Map β used since v4.0.0 for bot-level sub-agents (cron spawns, implicit Task tool children, `/sub-agents spawn` CLI)
|
|
@@ -392,7 +455,7 @@ This matches the OpenClaw experience the user was asking about β except it's b
|
|
|
392
455
|
|
|
393
456
|
### π Patch: recover partial output from interrupted background sub-agents
|
|
394
457
|
|
|
395
|
-
**The bug
|
|
458
|
+
**The bug the maintainer saw:** Two Telegram messages appeared hours apart: `β±οΈ Background agent a5bf8c74 timeout Β· 720m 3s Β· 0 in / 0 out` and `... ab9372d4 timeout Β· 720m 1s Β· 0 in / 0 out`, both with `(empty output)`. Three more agents were still pending, all interrupted mid-execution with hundreds of KB of real work sitting on disk.
|
|
396
459
|
|
|
397
460
|
**Root cause:** v4.12.3's bypass-abort calls `session.abortController.abort()`, which propagates through `claude-sdk-provider.ts`'s `internalAbortController` into the SDK's CLI subprocess, which in turn propagates into any in-flight `Agent(run_in_background: true)` tool executions. Evidence from the disk:
|
|
398
461
|
|
|
@@ -441,7 +504,7 @@ Result: on the next `pollOnce()` after v4.12.4 ships, the three stuck agents get
|
|
|
441
504
|
|
|
442
505
|
### π Patch: Background sub-agent no longer blocks the main Telegram session
|
|
443
506
|
|
|
444
|
-
**The bug
|
|
507
|
+
**The bug the maintainer reported:** After launching an async sub-agent (`run_in_background: true`), sending any follow-up message to the bot silently stalled for 2+ minutes before being processed. v4.12.1/v4.12.2 attempted a prompt-hint mitigation but did NOT address the architectural root cause.
|
|
445
508
|
|
|
446
509
|
**Root cause (re-diagnosed with live SDK event logs):** The Claude Agent SDK's CLI subprocess stays alive for the full duration of a background task so it can inject the `<task-notification>` inline into the NEXT assistant turn. While that subprocess idles, Alvin's query iterator is still being drained, `session.isProcessing` stays `true`, and every new user message gets pushed into the 3-slot queue β which doesn't auto-drain. From the user's perspective: send "A" β nothing happens for 2 minutes.
|
|
447
510
|
|
|
@@ -663,7 +726,7 @@ Both the platform handler (Slack/Discord/WhatsApp) and the Telegram main handler
|
|
|
663
726
|
|
|
664
727
|
#### P0 #4 β Slack Setup Documentation (`docs/install/slack-setup.md`, `docs/install/slack-manifest.json`)
|
|
665
728
|
|
|
666
|
-
Step-by-step guide: create Slack App from manifest β Socket Mode β App-Level Token β Bot Token β `~/.alvin-bot/.env` β restart β invite bot β create workspace files. Covers troubleshooting for common issues. The `slack-manifest.json` is copy-paste-ready: pre-configured bot user, all required scopes, event subscriptions, Socket Mode enabled. Both files are gitignored (
|
|
729
|
+
Step-by-step guide: create Slack App from manifest β Socket Mode β App-Level Token β Bot Token β `~/.alvin-bot/.env` β restart β invite bot β create workspace files. Covers troubleshooting for common issues. The `slack-manifest.json` is copy-paste-ready: pre-configured bot user, all required scopes, event subscriptions, Socket Mode enabled. Both files are gitignored (the maintainer's docs/install/ convention) and ship via GitHub Release assets.
|
|
667
730
|
|
|
668
731
|
#### P1 #1 β Slack Progress Ticker (`src/platforms/slack.ts`)
|
|
669
732
|
|
|
@@ -677,7 +740,7 @@ Step-by-step guide: create Slack App from manifest β Socket Mode β App-Level
|
|
|
677
740
|
|
|
678
741
|
`SlackAdapter.setTyping()` now calls `assistant.threads.setStatus` so Slack shows "Alvin is thinkingβ¦" under the message during long queries. Silently no-ops in channels where the assistant scope isn't granted.
|
|
679
742
|
|
|
680
|
-
New `SlackAdapter.getChannelName(channelId)` resolves + caches channel names via `conversations.info`. `platform-message.ts` detects this helper via duck-typing on the adapter and passes the resolved name to `resolveWorkspaceOrDefault` β enabling channel-name matching (`#
|
|
743
|
+
New `SlackAdapter.getChannelName(channelId)` resolves + caches channel names via `conversations.info`. `platform-message.ts` detects this helper via duck-typing on the adapter and passes the resolved name to `resolveWorkspaceOrDefault` β enabling channel-name matching (`#my-project` β `workspaces/my-project.md`) without hardcoding the Slack type in the platform handler.
|
|
681
744
|
|
|
682
745
|
#### P1 #3 β Telegram `/workspace` + `/workspaces` Commands
|
|
683
746
|
|
|
@@ -793,7 +856,7 @@ Inspired by Mem0's auto-extraction. When `compactSession()` archives old message
|
|
|
793
856
|
|
|
794
857
|
- **mempalace as MCP server: rejected.** Considered installing mempalace as a Python MCP service. Rejected because (1) Alvin is all-TypeScript and adding a 2nd Python service to launchd is operational complexity, (2) Alvin already has an embeddings vector index β mempalace would be a parallel duplicate, (3) mempalace's MCP tools are only consumed by the SDK; cron jobs, sub-agents, and non-SDK providers wouldn't see them. Conclusion: **adopt the patterns natively** (L0βL3 layering, AAAK-style structured extraction) rather than running a second service.
|
|
795
858
|
- **SQLite migration deferred.** The 128 MB JSON embeddings index is a known performance issue and is already noted in `~/.claude/projects/-Users-alvin-de/memory/project_alvinbot_sqlite_migration.md` for v4.12+. Orthogonal to the "frickelig nach Restart" UX problem this release targets.
|
|
796
|
-
- **Multi-user isolation deferred.** Memories are still global per data dir. Single-user use case, not a privacy concern for
|
|
859
|
+
- **Multi-user isolation deferred.** Memories are still global per data dir. Single-user use case, not a privacy concern for the maintainer's setup.
|
|
797
860
|
- **Decay/aging deferred.** Daily logs grow monotonically. Will be addressed alongside SQLite migration.
|
|
798
861
|
|
|
799
862
|
#### Testing
|
|
@@ -868,11 +931,11 @@ Live-verified via isolated SDK probe (`node sdk-probe.mjs` inside the repo) whic
|
|
|
868
931
|
|
|
869
932
|
#### What you'll see as a user
|
|
870
933
|
|
|
871
|
-
Send: *"Make a SEO audit of
|
|
934
|
+
Send: *"Make a SEO audit of example.com and example.com in parallel"*
|
|
872
935
|
|
|
873
936
|
- **0 s** β Claude responds: *"Starting both audits in the background β I'll send the reports when done."* Main session **unlocks**.
|
|
874
937
|
- **1β10 min later** β You can chat about anything else. The bot answers immediately.
|
|
875
|
-
- **~13 min** (when each agent finishes) β Two separate banner messages arrive: *"β
SEO audit
|
|
938
|
+
- **~13 min** (when each agent finishes) β Two separate banner messages arrive: *"β
SEO audit example.com completed Β· 13m 17s Β· 2.6M in / 28k out"* + the full report body, delivered via the v4.9.3 Markdownβplain-text fallback path.
|
|
876
939
|
|
|
877
940
|
#### Non-goals
|
|
878
941
|
|
|
@@ -931,7 +994,7 @@ He was right. My v4.9.0 `stopWebServer()` fix was *prevention* β it stopped th
|
|
|
931
994
|
|
|
932
995
|
### π Two UX bugs found in production after v4.9.2 β now closed
|
|
933
996
|
|
|
934
|
-
|
|
997
|
+
the maintainer triggered `/cron run Daily Job Alert` after the v4.9.2 deploy and saw 13 minutes of chat silence followed by nothing. Forensics on the live bot revealed two distinct problems on top of an already-successful run:
|
|
935
998
|
|
|
936
999
|
**1. `subagent-delivery` has been silently dropping every banner for days.** Err.log: `GrammyError: Call to 'sendMessage' failed! (400: Bad Request: can't parse entities: Can't find end of the entity starting at byte offset 2636)`. The daily-job-alert sub-agent produces markdown-dense output (`|` tables, `**bold**`, `\|` escapes, mixed asterisks). Telegram's Markdown parser refuses it, `api.sendMessage(..., parse_mode: "Markdown")` throws, and the bare try/catch in `deliverSubAgentResult` logs + bails. **Result: the user has never seen a sub-agent-delivery banner, even when the underlying run succeeded perfectly and emailed the HTML report correctly.**
|
|
937
1000
|
|
|
@@ -1060,12 +1123,12 @@ The `browse` skill used to instruct the agent to start `node scripts/browse-serv
|
|
|
1060
1123
|
- **Tier 1** β `browser.sh stealth <url>` (Playwright + stealth plugin, headless, Cloudflare-masking)
|
|
1061
1124
|
- **Tier 2** β `browser.sh cdp {start|goto|shot|tabs|stop}` (real Chrome with persistent profile at `~/.claude/hub/BROWSER/profile/`, login cookies survive restarts)
|
|
1062
1125
|
- **Tier 3** β Claude-in-Chrome extension via MCP tools (interactive CLI only)
|
|
1063
|
-
- Explicit escalation ladder (WebFetch β stealth β CDP β ask
|
|
1126
|
+
- Explicit escalation ladder (WebFetch β stealth β CDP β ask the maintainer to log in) and a `NIEMALS browse-server.cjs nutzen` anti-rule.
|
|
1064
1127
|
- Concrete working targets (StepStone β
, Michael Page β
, LinkedIn β
with login, Indeed β) so the agent knows what to try where.
|
|
1065
1128
|
|
|
1066
1129
|
- **`src/services/browser-manager.ts` β hardened fallback chain.** The multi-strategy manager already had the right *shape* (`gateway β cdp β hub-stealth β cli`) but several ops silently broke or hung:
|
|
1067
1130
|
- **`gatewayRequest` now has a 15 s timeout** (`req.destroy` on elapse). Previously a hung gateway would wedge the caller forever.
|
|
1068
|
-
- **CDP fallback for interactive ops.** `click`, `fill`, `type`, `press`, `scroll`, `evaluate`, `info`, and `getTree` used to hard-throw `"requires gateway"` when `browse-server.cjs` wasn't running. They now try the gateway first, then a short-lived `chromium.connectOverCDP()` via a new `withCdpPage()` helper that reuses
|
|
1131
|
+
- **CDP fallback for interactive ops.** `click`, `fill`, `type`, `press`, `scroll`, `evaluate`, `info`, and `getTree` used to hard-throw `"requires gateway"` when `browse-server.cjs` wasn't running. They now try the gateway first, then a short-lived `chromium.connectOverCDP()` via a new `withCdpPage()` helper that reuses the maintainer's live Chrome on port 9222. Refs are interpreted as CSS selectors when gateway is absent.
|
|
1069
1132
|
- **Explicit PNG extension** on auto-generated screenshot filenames (`shot_<ts>.png`) so Playwright's format inference is unambiguous.
|
|
1070
1133
|
- **Better error messages** β every "needs interactive" throw now includes the exact command to start CDP Chrome (`~/.claude/hub/SCRIPTS/browser.sh cdp start headless`).
|
|
1071
1134
|
|
|
@@ -1094,7 +1157,7 @@ Sub-agents and `ai-query` cron jobs used to hard-cap at 5 minutes (`SUBAGENT_TIM
|
|
|
1094
1157
|
|
|
1095
1158
|
### π Silenced harmless `message is not modified` Telegram errors
|
|
1096
1159
|
|
|
1097
|
-
Occasionally
|
|
1160
|
+
Occasionally the maintainer would see a red banner at the bottom of an Alvin message:
|
|
1098
1161
|
|
|
1099
1162
|
> Error: Call to 'editMessageText' failed! (400: Bad Request: message is not modified: specified new message content and reply markup are exactly the same as a current content and reply markup of the message)
|
|
1100
1163
|
|
|
@@ -1129,7 +1192,7 @@ After 4.8.7, running `/update` after a manual rebuild will correctly say *"Disk
|
|
|
1129
1192
|
|
|
1130
1193
|
### β¨ Internal watchdog with crash-loop brake (`src/services/watchdog.ts`)
|
|
1131
1194
|
|
|
1132
|
-
|
|
1195
|
+
the maintainer asked for "derbe persistent" β already 95% there with `KeepAlive: true` from 4.8.6, but the missing piece was a brake to stop the bot from infinite-restart-looping if a deterministic crash happens (corrupt state file, missing dependency, broken upgrade).
|
|
1133
1196
|
|
|
1134
1197
|
**New module**: `src/services/watchdog.ts`. Two responsibilities:
|
|
1135
1198
|
|
|
@@ -1244,7 +1307,7 @@ After 4.8.5, `/update` on the test MacBook will correctly detect the npm install
|
|
|
1244
1307
|
|
|
1245
1308
|
### π WhatsApp self-chat detection for the new `@lid` identity format
|
|
1246
1309
|
|
|
1247
|
-
|
|
1310
|
+
the maintainer reported that the WhatsApp bot wasn't responding to "Hi" in his self-chat even after enabling both `Self-chat only` and `Reply to private messages` in the Web UI. Debug logging showed the bot receiving the message correctly and detecting `fromMe=true`, but then hitting the "skip: own message in group/DM" branch because `isSelfChat()` was returning `false`.
|
|
1248
1311
|
|
|
1249
1312
|
**Root cause**: WhatsApp has rolled out a new privacy feature that replaces phone-number JIDs in self-chats (and some groups) with a **LID β Linked Identity**. Instead of `4917661236656@s.whatsapp.net`, messages in a self-chat now arrive with `jid = "162805718225143@lid"` β a completely opaque identifier that looks nothing like the phone number.
|
|
1250
1313
|
|
|
@@ -1316,7 +1379,7 @@ Offline-friendly status command β no running bot required. Prints:
|
|
|
1316
1379
|
- On Linux/Windows: `pm2 jlist` check for the `alvin-bot` process
|
|
1317
1380
|
- **Live info** (when the bot is running with the web UI on :3100): Uptime, active model
|
|
1318
1381
|
|
|
1319
|
-
Answers
|
|
1382
|
+
Answers the maintainer's request: *"alvin-bot status im Terminal soll auch die Version anzeigen"*. The command prominently features the version at the top so it's the first thing you see.
|
|
1320
1383
|
|
|
1321
1384
|
Example:
|
|
1322
1385
|
|
|
@@ -1419,7 +1482,7 @@ New behavior in `bin/cli.js`:
|
|
|
1419
1482
|
- **pm2 now empty** β *"pm2 now has zero managed processes. Remove it with: `npm uninstall -g pm2`"*
|
|
1420
1483
|
- **pm2 still has other projects** β *"pm2 still has other projects running β leaving it installed."*
|
|
1421
1484
|
|
|
1422
|
-
Caught immediately after 4.7.0 shipped when
|
|
1485
|
+
Caught immediately after 4.7.0 shipped when the maintainer pointed out his Mac mini has `polyseus` in pm2 alongside `alvin-bot` and didn't want it touched.
|
|
1423
1486
|
|
|
1424
1487
|
## [4.7.0] β 2026-04-11
|
|
1425
1488
|
|
|
@@ -1799,7 +1862,7 @@ Remaining unaddressed (by design, require breaking upgrades or overrides):
|
|
|
1799
1862
|
|
|
1800
1863
|
### β¨ Stability Improvements
|
|
1801
1864
|
|
|
1802
|
-
**Session memory hygiene (`src/services/session.ts`)** β The in-memory `sessions` Map grew unbounded: every user that ever messaged the bot kept a full session object (including conversation history, cost breakdown, abort controller) forever. On a single-user bot like
|
|
1865
|
+
**Session memory hygiene (`src/services/session.ts`)** β The in-memory `sessions` Map grew unbounded: every user that ever messaged the bot kept a full session object (including conversation history, cost breakdown, abort controller) forever. On a single-user bot like the maintainer's this is a non-issue; on any multi-user deployment it's a steady leak.
|
|
1803
1866
|
|
|
1804
1867
|
New behavior:
|
|
1805
1868
|
- **Conservative 7-day TTL**: a session is only eligible for cleanup after 7 full days of complete inactivity. Configurable via `ALVIN_SESSION_TTL_DAYS` env var.
|
package/README.md
CHANGED
|
@@ -335,32 +335,32 @@ alvin-bot/
|
|
|
335
335
|
|
|
336
336
|
### Why you'd want this
|
|
337
337
|
|
|
338
|
-
Without workspaces, Alvin has one big blob of context. If you ask about
|
|
338
|
+
Without workspaces, Alvin has one big blob of context. If you ask about one project's deployment right after debugging a completely unrelated service, Claude pollutes one context with the other. Workspaces solve this: **Slack channel = session**, or on Telegram, **`/workspace my-project` = session**. Each one has its own Claude SDK `resume` token, history, and current project CLAUDE.md loaded via its working directory.
|
|
339
339
|
|
|
340
340
|
### How it works
|
|
341
341
|
|
|
342
342
|
1. **Drop a markdown file** into `~/.alvin-bot/workspaces/<name>.md` with YAML frontmatter.
|
|
343
343
|
2. **Alvin hot-reloads** the workspace registry (no restart needed β same pattern as skills).
|
|
344
|
-
3. On **Slack**, workspaces resolve by explicit channel ID first, then by channel name match (`#
|
|
344
|
+
3. On **Slack**, workspaces resolve by explicit channel ID first, then by channel name match (`#my-project` β `workspaces/my-project.md`, case-insensitive).
|
|
345
345
|
4. On **Telegram**, run `/workspace <name>` to switch β next message uses the new persona and cwd.
|
|
346
346
|
5. Nothing configured? Alvin falls back to the "default" workspace exactly like pre-v4.12 β **no breaking changes**.
|
|
347
347
|
|
|
348
348
|
### Example workspace file
|
|
349
349
|
|
|
350
|
-
Create `~/.alvin-bot/workspaces/
|
|
350
|
+
Create `~/.alvin-bot/workspaces/my-project.md`:
|
|
351
351
|
|
|
352
352
|
```markdown
|
|
353
353
|
---
|
|
354
|
-
purpose:
|
|
355
|
-
cwd: ~/Projects/
|
|
354
|
+
purpose: my-project website dev
|
|
355
|
+
cwd: ~/Projects/my-project
|
|
356
356
|
emoji: "π’"
|
|
357
357
|
color: "#6366f1"
|
|
358
358
|
channels: ["C01ABCDEF"]
|
|
359
359
|
---
|
|
360
|
-
You are focused on the
|
|
361
|
-
Drizzle + MySQL. Production VPS
|
|
362
|
-
concise, directly actionable answers about features, deployment,
|
|
363
|
-
Stripe integration.
|
|
360
|
+
You are focused on the my-project website. Stack: React + Express +
|
|
361
|
+
Drizzle + MySQL. Production VPS at your-vps.example.com, deploy via rsync.
|
|
362
|
+
Prefer concise, directly actionable answers about features, deployment,
|
|
363
|
+
and Stripe integration.
|
|
364
364
|
```
|
|
365
365
|
|
|
366
366
|
The `cwd` auto-loads the project-specific `CLAUDE.md` via Claude SDK's `settingSources: ["user", "project"]`, so each workspace inherits its project's conventions automatically. `channels` is optional β omit it to match by filename.
|
|
@@ -405,7 +405,7 @@ curl -s http://localhost:3100/api/workspaces | jq
|
|
|
405
405
|
|
|
406
406
|
### Architecture guarantees
|
|
407
407
|
|
|
408
|
-
- **Memory is global.** Facts Alvin learns in
|
|
408
|
+
- **Memory is global.** Facts Alvin learns in one workspace are visible in every other workspace via the shared `MEMORY.md` and embeddings index. Per-workspace memory layer is on the v4.13 roadmap.
|
|
409
409
|
- **Sub-agents are per-session.** Each workspace can dispatch its own detached sub-agents via `alvin_dispatch_agent` β results come back to the originating channel on any platform (Telegram, Slack, Discord, WhatsApp), visible in `/subagents list` (v4.13.0+ dispatch, v4.14.0 cross-platform, v4.14.1 unified list view).
|
|
410
410
|
- **Session state survives restart.** Claude SDK `resume` tokens, conversation history, language, effort, and `workspaceName` all persist via `session-persistence.ts` (v4.11.0).
|
|
411
411
|
- **Backwards compatible.** If you don't create any workspace files, everything behaves exactly like v4.11. Upgrade is a no-op.
|
|
@@ -658,11 +658,11 @@ alvin-bot version # Show version
|
|
|
658
658
|
- [x] Watcher zombie guard β missing outputFile > 10 min delivers as failed instead of 12h timeout (v4.14.2)
|
|
659
659
|
- [x] Staleness-based partial output recovery for interrupted sub-agents (v4.12.4)
|
|
660
660
|
- [ ] SQLite migration of the embeddings index (currently 128 MB JSON)
|
|
661
|
-
- [ ] Per-workspace memory layer (additive over global) β facts learned in
|
|
662
|
-
- [ ] Per-workspace provider override (`provider:` in frontmatter) β e.g.
|
|
661
|
+
- [ ] Per-workspace memory layer (additive over global) β facts learned in one workspace stay there unless explicitly promoted to global
|
|
662
|
+
- [ ] Per-workspace provider override (`provider:` in frontmatter) β e.g. one workspace uses Claude Opus, another uses a cheaper model
|
|
663
663
|
- [ ] Per-workspace skill allowlist β scope Apple Notes to personal workspace, sysadmin only to devops workspace, etc.
|
|
664
664
|
- [ ] Multi-User Slack (real `per-channel-peer` mode) β different users in the same Slack channel get their own sub-sessions
|
|
665
|
-
- [ ] Workspace cloning / templates β `/workspace clone
|
|
665
|
+
- [ ] Workspace cloning / templates β `/workspace clone my-project as my-fork` spins up a new workspace from an existing one
|
|
666
666
|
- [ ] Daily log decay / archive β older daily logs move to cold storage after N days
|
|
667
667
|
- [ ] **Phase 18** β Security + Platform hardening (from v4.12.1 audit, prioritized)
|
|
668
668
|
- [ ] **P1 β Electron major upgrade** (35 β 41+) β fixes 1 HIGH + 5 MODERATE Electron CVEs in the Desktop-Build path. Major version jump, requires full rebuild + test of `.dmg` flow. Separate release (likely bundled with Windows `.exe` work).
|
package/bin/cli.js
CHANGED
|
@@ -1928,6 +1928,129 @@ switch (cmd) {
|
|
|
1928
1928
|
console.log("");
|
|
1929
1929
|
process.exit(0);
|
|
1930
1930
|
}
|
|
1931
|
+
case "browser": {
|
|
1932
|
+
// Browser subcommands: wraps cdp-bootstrap so Skills + humans have a
|
|
1933
|
+
// stable shell interface that works everywhere the bot is installed.
|
|
1934
|
+
const sub = process.argv[3];
|
|
1935
|
+
const { dist } = await import("../dist/services/cdp-bootstrap.js").then(
|
|
1936
|
+
(m) => ({ dist: m }),
|
|
1937
|
+
async () => {
|
|
1938
|
+
console.error("β dist/services/cdp-bootstrap.js not found. Run: npm run build");
|
|
1939
|
+
process.exit(1);
|
|
1940
|
+
}
|
|
1941
|
+
);
|
|
1942
|
+
try {
|
|
1943
|
+
switch (sub) {
|
|
1944
|
+
case "start": {
|
|
1945
|
+
const mode = process.argv[4] === "headful" ? "headful" : "headless";
|
|
1946
|
+
const st = await dist.ensureRunning({ mode });
|
|
1947
|
+
console.log(`β
CDP running β PID ${st.pid} β ${st.endpoint}`);
|
|
1948
|
+
if (st.binary) console.log(` Binary: ${st.binary}`);
|
|
1949
|
+
break;
|
|
1950
|
+
}
|
|
1951
|
+
case "stop": {
|
|
1952
|
+
await dist.stop();
|
|
1953
|
+
console.log("β
CDP stopped");
|
|
1954
|
+
break;
|
|
1955
|
+
}
|
|
1956
|
+
case "status": {
|
|
1957
|
+
const st = await dist.status();
|
|
1958
|
+
if (st.running) {
|
|
1959
|
+
console.log(`β
CDP running β PID ${st.pid}`);
|
|
1960
|
+
} else {
|
|
1961
|
+
console.log(`β CDP not running: ${st.reason || "unknown"}`);
|
|
1962
|
+
}
|
|
1963
|
+
if (st.binary) console.log(` Binary: ${st.binary}`);
|
|
1964
|
+
console.log(` Endpoint: ${st.endpoint}`);
|
|
1965
|
+
break;
|
|
1966
|
+
}
|
|
1967
|
+
case "doctor": {
|
|
1968
|
+
const rep = await dist.doctor();
|
|
1969
|
+
console.log("=== Browser Doctor ===\n");
|
|
1970
|
+
for (const c of rep.checks) {
|
|
1971
|
+
console.log(`${c.ok ? "β
" : "β"} ${c.name}: ${c.detail}`);
|
|
1972
|
+
}
|
|
1973
|
+
console.log(rep.ok ? "\nAll checks passed." : "\nSome checks failed β see above.");
|
|
1974
|
+
process.exit(rep.ok ? 0 : 1);
|
|
1975
|
+
}
|
|
1976
|
+
case "goto":
|
|
1977
|
+
case "shot":
|
|
1978
|
+
case "screenshot":
|
|
1979
|
+
case "tabs":
|
|
1980
|
+
case "eval": {
|
|
1981
|
+
await dist.ensureRunning({ mode: "headless" });
|
|
1982
|
+
const { chromium } = await import("playwright").catch(() => ({ chromium: null }));
|
|
1983
|
+
if (!chromium) {
|
|
1984
|
+
console.error("β playwright not available. Run: npm install");
|
|
1985
|
+
process.exit(1);
|
|
1986
|
+
}
|
|
1987
|
+
const browser = await chromium.connectOverCDP("http://127.0.0.1:9222");
|
|
1988
|
+
try {
|
|
1989
|
+
if (sub === "tabs") {
|
|
1990
|
+
const tabs = [];
|
|
1991
|
+
for (const ctx of browser.contexts()) {
|
|
1992
|
+
for (const page of ctx.pages()) {
|
|
1993
|
+
tabs.push({ title: await page.title(), url: page.url() });
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
console.log(JSON.stringify(tabs, null, 2));
|
|
1997
|
+
break;
|
|
1998
|
+
}
|
|
1999
|
+
const url = process.argv[4];
|
|
2000
|
+
if (!url) {
|
|
2001
|
+
console.error(`Usage: alvin-bot browser ${sub} <url> [args]`);
|
|
2002
|
+
process.exit(1);
|
|
2003
|
+
}
|
|
2004
|
+
const ctx = browser.contexts()[0] || (await browser.newContext());
|
|
2005
|
+
const page = await ctx.newPage();
|
|
2006
|
+
try {
|
|
2007
|
+
await page.goto(url, { waitUntil: "domcontentloaded", timeout: 30000 });
|
|
2008
|
+
if (sub === "goto") {
|
|
2009
|
+
console.log(JSON.stringify({ url: page.url(), title: await page.title() }, null, 2));
|
|
2010
|
+
} else if (sub === "shot" || sub === "screenshot") {
|
|
2011
|
+
const name = process.argv[5] || `shot_${Date.now()}.png`;
|
|
2012
|
+
const { CDP_SCREENSHOTS_DIR } = await import("../dist/paths.js");
|
|
2013
|
+
const out = name.startsWith("/") ? name : `${CDP_SCREENSHOTS_DIR}/${name}`;
|
|
2014
|
+
await page.screenshot({ path: out, fullPage: true });
|
|
2015
|
+
console.log(JSON.stringify({ url: page.url(), title: await page.title(), screenshot: out }, null, 2));
|
|
2016
|
+
} else if (sub === "eval") {
|
|
2017
|
+
const js = process.argv[5] || "document.title";
|
|
2018
|
+
const result = await page.evaluate(new Function(`return (${js})`));
|
|
2019
|
+
console.log(JSON.stringify({ url: page.url(), result }, null, 2));
|
|
2020
|
+
}
|
|
2021
|
+
} finally {
|
|
2022
|
+
await page.close();
|
|
2023
|
+
}
|
|
2024
|
+
} finally {
|
|
2025
|
+
await browser.close();
|
|
2026
|
+
}
|
|
2027
|
+
break;
|
|
2028
|
+
}
|
|
2029
|
+
default:
|
|
2030
|
+
console.log(`alvin-bot browser β bot-managed Chromium (CDP on port 9222)
|
|
2031
|
+
|
|
2032
|
+
start [headful|headless] Start Chromium with CDP (default: headless)
|
|
2033
|
+
stop Stop the bot-managed Chromium
|
|
2034
|
+
status Show PID + binary + endpoint
|
|
2035
|
+
doctor Diagnose common issues
|
|
2036
|
+
goto <url> Navigate and print page info as JSON
|
|
2037
|
+
shot <url> [filename] Screenshot to ~/.alvin-bot/browser/screenshots/
|
|
2038
|
+
eval <url> <js> Evaluate JS expression in page context
|
|
2039
|
+
tabs List all open tabs
|
|
2040
|
+
|
|
2041
|
+
Notes:
|
|
2042
|
+
β’ Uses Playwright's bundled Chromium β no conflict with your normal Chrome.
|
|
2043
|
+
β’ Profile persists at ~/.alvin-bot/browser/profile/ (cookies survive restarts).
|
|
2044
|
+
β’ First run needs: npx playwright install chromium
|
|
2045
|
+
`);
|
|
2046
|
+
process.exit(sub ? 1 : 0);
|
|
2047
|
+
}
|
|
2048
|
+
} catch (err) {
|
|
2049
|
+
console.error(`β ${err.message || err}`);
|
|
2050
|
+
process.exit(1);
|
|
2051
|
+
}
|
|
2052
|
+
break;
|
|
2053
|
+
}
|
|
1931
2054
|
default:
|
|
1932
2055
|
console.log(`
|
|
1933
2056
|
${t("cli.title")}
|
|
@@ -1939,6 +2062,7 @@ ${t("cli.commands")}
|
|
|
1939
2062
|
doctor ${t("cli.doctorDesc")}
|
|
1940
2063
|
audit Security health check (permissions, secrets, config)
|
|
1941
2064
|
search Search your assets, memories, and skills
|
|
2065
|
+
browser Manage bot-owned Chromium (start/stop/goto/shot/doctor)
|
|
1942
2066
|
update ${t("cli.updateDesc")}
|
|
1943
2067
|
start ${t("cli.startDesc")} (background via PM2)
|
|
1944
2068
|
start -f Start in foreground (for debugging)
|
|
@@ -100,8 +100,8 @@ export async function handlePlatformMessage(msg, adapter) {
|
|
|
100
100
|
touchProfile(profileKey, msg.userName, msg.userHandle, msg.platform, text);
|
|
101
101
|
// v4.12.0 β Workspace resolution: channel β workspace β persona + cwd.
|
|
102
102
|
// P1 #2 β If the platform has a getChannelName helper (Slack does), use
|
|
103
|
-
// it to enable channel-name-based workspace matching (e.g. #
|
|
104
|
-
// workspaces/
|
|
103
|
+
// it to enable channel-name-based workspace matching (e.g. #my-project β
|
|
104
|
+
// workspaces/my-project.md). Cached in the adapter, so no extra API call
|
|
105
105
|
// after the first hit per channel.
|
|
106
106
|
let channelName;
|
|
107
107
|
const getChannelName = adapter.getChannelName;
|
package/dist/paths.js
CHANGED
|
@@ -108,11 +108,21 @@ export const AGENTS_FILE = resolve(DATA_DIR, "AGENTS.md");
|
|
|
108
108
|
export const HOOKS_DIR = resolve(DATA_DIR, "hooks");
|
|
109
109
|
/** scripts/browse-server.cjs β HTTP gateway for persistent browser sessions */
|
|
110
110
|
export const BROWSE_SERVER_SCRIPT = resolve(BOT_ROOT, "scripts", "browse-server.cjs");
|
|
111
|
-
/** ~/.claude/hub/SCRIPTS/browser.sh β
|
|
111
|
+
/** ~/.claude/hub/SCRIPTS/browser.sh β Optional dev-only 3-tier browser router.
|
|
112
|
+
* Used ONLY if present (maintainer dev environment). Not required for normal operation β
|
|
113
|
+
* the bot has its own CDP bootstrap (see src/services/cdp-bootstrap.ts). */
|
|
112
114
|
export const HUB_BROWSER_SH = resolve(os.homedir(), ".claude", "hub", "SCRIPTS", "browser.sh");
|
|
115
|
+
/** browser/profile/ β Persistent Chromium profile for CDP (cookies, login state) */
|
|
116
|
+
export const CDP_PROFILE_DIR = resolve(DATA_DIR, "browser", "profile");
|
|
117
|
+
/** browser/screenshots/ β CDP screenshot output directory */
|
|
118
|
+
export const CDP_SCREENSHOTS_DIR = resolve(DATA_DIR, "browser", "screenshots");
|
|
119
|
+
/** browser/chrome-cdp.pid β PID of Chromium started by cdp-bootstrap */
|
|
120
|
+
export const CDP_PID_FILE = resolve(DATA_DIR, "browser", "chrome-cdp.pid");
|
|
121
|
+
/** browser/chrome-cdp.log β Chromium stderr/stdout when started by cdp-bootstrap */
|
|
122
|
+
export const CDP_LOG_FILE = resolve(DATA_DIR, "browser", "chrome-cdp.log");
|
|
113
123
|
/** data/exec-allowlist.json β User-defined exec allowlist */
|
|
114
124
|
export const EXEC_ALLOWLIST_FILE = resolve(DATA_DIR, "exec-allowlist.json");
|
|
115
|
-
/** assets/ β User
|
|
125
|
+
/** assets/ β User-supplied files organized in category subdirectories */
|
|
116
126
|
export const ASSETS_DIR = resolve(DATA_DIR, "assets");
|
|
117
127
|
/** assets/INDEX.json β Machine-readable asset registry */
|
|
118
128
|
export const ASSETS_INDEX_JSON = resolve(DATA_DIR, "assets", "INDEX.json");
|
|
@@ -393,6 +393,12 @@ export class ClaudeSDKProvider {
|
|
|
393
393
|
return cache(false);
|
|
394
394
|
}
|
|
395
395
|
}
|
|
396
|
+
/** v4.15.2 β Clear the cached isAvailable() result. Called by the
|
|
397
|
+
* heartbeat service after detecting macOS sleep/wake so the first
|
|
398
|
+
* post-wake probe doesn't serve a stale "unavailable" from hours ago. */
|
|
399
|
+
invalidateAvailabilityCache() {
|
|
400
|
+
this.availabilityCache = null;
|
|
401
|
+
}
|
|
396
402
|
getInfo() {
|
|
397
403
|
const model = this.config.model === "inherit"
|
|
398
404
|
? "CLI default (latest)"
|
|
@@ -61,7 +61,7 @@ export function buildAlvinMcpServer(ctx) {
|
|
|
61
61
|
.describe("The full prompt for the sub-agent. Be specific and self-contained β the sub-agent has no access to this conversation's context and will see only this prompt."),
|
|
62
62
|
description: z
|
|
63
63
|
.string()
|
|
64
|
-
.describe("Short human-readable title (e.g. 'SEO audit
|
|
64
|
+
.describe("Short human-readable title (e.g. 'SEO audit example.com', 'Research topic X'). Shown to the user when the result arrives."),
|
|
65
65
|
}, async (args) => {
|
|
66
66
|
try {
|
|
67
67
|
const result = dispatchDetachedAgent({
|
|
@@ -50,22 +50,16 @@ function walkDir(dir) {
|
|
|
50
50
|
}
|
|
51
51
|
/**
|
|
52
52
|
* Generate a human-readable description from a filename.
|
|
53
|
-
* "acme-cover-letter.html" β "Cover Letter: Acme"
|
|
54
53
|
* "profile-photo.jpeg" β "Profile Photo"
|
|
54
|
+
* "my-document.html" β "My Document"
|
|
55
55
|
*/
|
|
56
56
|
function descriptionFromFilename(filename, category) {
|
|
57
57
|
const name = filename.replace(/\.[^.]+$/, ""); // strip extension
|
|
58
58
|
const words = name.replace(/[-_]/g, " ").trim();
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
if (category === "cv-templates") {
|
|
65
|
-
return `CV Template: ${words}`;
|
|
66
|
-
}
|
|
67
|
-
// Default: capitalize words
|
|
68
|
-
return words.replace(/\b\w/g, c => c.toUpperCase());
|
|
59
|
+
// Prefix with capitalized category for disambiguation when the filename alone is terse
|
|
60
|
+
const prefix = category ? category.replace(/[-_]/g, " ").replace(/\b\w/g, c => c.toUpperCase()) + ": " : "";
|
|
61
|
+
const title = words.replace(/\b\w/g, c => c.toUpperCase());
|
|
62
|
+
return prefix ? `${prefix}${title}` : title;
|
|
69
63
|
}
|
|
70
64
|
/**
|
|
71
65
|
* Determine category for a file.
|
|
@@ -17,6 +17,7 @@ import { config } from "../config.js";
|
|
|
17
17
|
import { BROWSE_SERVER_SCRIPT, HUB_BROWSER_SH } from "../paths.js";
|
|
18
18
|
import { screenshotUrl, extractText, generatePdf } from "./browser.js";
|
|
19
19
|
import { webfetchNavigate, WebfetchFailed } from "./browser-webfetch.js";
|
|
20
|
+
import * as cdpBootstrap from "./cdp-bootstrap.js";
|
|
20
21
|
const CDP_PORT = 9222;
|
|
21
22
|
const EXEC_TIMEOUT = 60_000; // 60s for page loads via shell
|
|
22
23
|
// ββ Logging ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -125,23 +126,35 @@ export async function resolveStrategy(preferred) {
|
|
|
125
126
|
case "cdp":
|
|
126
127
|
if (await isCDPAvailable())
|
|
127
128
|
return "cdp";
|
|
128
|
-
//
|
|
129
|
+
// Bot-owned bootstrap is the primary path β works for every install,
|
|
130
|
+
// no Hub dependency, no conflict with user's own Chrome.
|
|
131
|
+
try {
|
|
132
|
+
log("CDP not running β starting bot-managed Chromium via cdp-bootstrap...");
|
|
133
|
+
await cdpBootstrap.ensureRunning({ mode: "headless" });
|
|
134
|
+
if (await isCDPAvailable()) {
|
|
135
|
+
log("CDP bootstrap started successfully.");
|
|
136
|
+
return "cdp";
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
log(`CDP bootstrap failed: ${err.message}`);
|
|
141
|
+
}
|
|
142
|
+
// Dev-only fallback: maintainer Hub script, if present
|
|
129
143
|
if (isHubBrowserAvailable()) {
|
|
130
144
|
try {
|
|
131
|
-
log("
|
|
145
|
+
log("Trying Hub script as fallback...");
|
|
132
146
|
execSync(`"${HUB_BROWSER_SH}" cdp start headless`, {
|
|
133
147
|
stdio: "pipe",
|
|
134
148
|
timeout: 15_000,
|
|
135
149
|
});
|
|
136
|
-
// Give it a moment to spin up
|
|
137
150
|
await new Promise((r) => setTimeout(r, 3000));
|
|
138
151
|
if (await isCDPAvailable()) {
|
|
139
|
-
log("CDP
|
|
152
|
+
log("CDP via Hub script.");
|
|
140
153
|
return "cdp";
|
|
141
154
|
}
|
|
142
155
|
}
|
|
143
156
|
catch (err) {
|
|
144
|
-
log(`
|
|
157
|
+
log(`Hub script fallback failed: ${err.message}`);
|
|
145
158
|
}
|
|
146
159
|
}
|
|
147
160
|
log("CDP unavailable. Falling back.");
|
|
@@ -385,7 +398,7 @@ async function withCdpPage(fn) {
|
|
|
385
398
|
await browser.close(); // Closes CDP connection, not Chrome itself
|
|
386
399
|
}
|
|
387
400
|
}
|
|
388
|
-
const NEEDS_INTERACTIVE_HINT = "Start CDP
|
|
401
|
+
const NEEDS_INTERACTIVE_HINT = "Start CDP: alvin-bot browser start (headless by default)";
|
|
389
402
|
/**
|
|
390
403
|
* Get accessibility tree (gateway preferred, CDP fallback returns outerHTML).
|
|
391
404
|
* The @eN ref model only exists in the gateway; under CDP we return a
|