alvin-bot 4.15.2 β 4.16.1
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 +70 -18
- package/README.md +13 -13
- package/bin/cli.js +124 -0
- package/dist/handlers/commands.js +12 -0
- package/dist/handlers/platform-message.js +2 -2
- package/dist/paths.js +12 -2
- 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/memory-layers.js +1 -1
- package/dist/services/personality.js +1 -1
- package/dist/services/release-highlights.js +79 -0
- 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,58 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Alvin Bot are documented here.
|
|
4
4
|
|
|
5
|
+
## [4.16.1] β 2026-04-20
|
|
6
|
+
|
|
7
|
+
### π Feature: /update shows release highlights
|
|
8
|
+
|
|
9
|
+
After a successful `/update`, the bot now sends a second short message with a bullet-point summary of what actually changed in the newly installed version. Pulled from the CHANGELOG entry matching the version string in the update result.
|
|
10
|
+
|
|
11
|
+
**Implementation:**
|
|
12
|
+
- New module `src/services/release-highlights.ts` parses the CHANGELOG block for a given version and returns at most 5 bullet points, β€500 chars total.
|
|
13
|
+
- Strategy: prefer `### ` subsection headlines (feature/fix titles); fall back to first non-empty paragraph lines.
|
|
14
|
+
- Telegram-friendly output: plain bullets (`β’ ...`), no tables, no code blocks, truncates gracefully with an ellipsis line if too long.
|
|
15
|
+
|
|
16
|
+
**Result format in chat:**
|
|
17
|
+
```
|
|
18
|
+
β
Installed v4.16.1 (was v4.16.0). Restarting...
|
|
19
|
+
π What's new in v4.16.1
|
|
20
|
+
|
|
21
|
+
β’ Feature: /update shows release highlights
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## [4.16.0] β 2026-04-20
|
|
25
|
+
|
|
26
|
+
### π Feature: bot-owned CDP Chromium β no more hub dependency
|
|
27
|
+
|
|
28
|
+
**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.
|
|
29
|
+
|
|
30
|
+
**Fix β three additions:**
|
|
31
|
+
|
|
32
|
+
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.
|
|
33
|
+
|
|
34
|
+
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/`.
|
|
35
|
+
|
|
36
|
+
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.
|
|
37
|
+
|
|
38
|
+
**Skills updated:**
|
|
39
|
+
- `skills/browse/SKILL.md` β rewritten to use `alvin-bot browser ...` commands; hub-script references removed (kept as "if present" note for dev environments).
|
|
40
|
+
- `skills/social-fetch/SKILL.md` β CDP fallback line uses `alvin-bot browser goto/shot`.
|
|
41
|
+
|
|
42
|
+
**Docs:**
|
|
43
|
+
- `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).
|
|
44
|
+
- `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/`.
|
|
45
|
+
|
|
46
|
+
**First-run setup (one-time):**
|
|
47
|
+
```bash
|
|
48
|
+
npx playwright install chromium
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Verified on 2026-04-20 with user's daily Chrome running:**
|
|
52
|
+
- `alvin-bot browser start` β PID + endpoint, no LaunchServices hijack
|
|
53
|
+
- `alvin-bot browser stop` + immediate `alvin-bot browser shot <url>` β CDP auto-starts, screenshot written (15 KB PNG in `~/.alvin-bot/browser/screenshots/`)
|
|
54
|
+
- `alvin-bot browser doctor` β all 4 checks green (binary, endpoint, PID, profile lock)
|
|
55
|
+
- `npm test` β 504/504 tests passing
|
|
56
|
+
|
|
5
57
|
## [4.15.2] β 2026-04-17
|
|
6
58
|
|
|
7
59
|
### π Fix: sleep-aware heartbeat prevents false failover after macOS wake
|
|
@@ -124,7 +176,7 @@ Four hardcoded Claude model IDs replaced with current strings: `claude-sonnet-4-
|
|
|
124
176
|
|
|
125
177
|
### π Patch: watcher zombie-entry fix (missing outputFile > 10 min = failed)
|
|
126
178
|
|
|
127
|
-
**Edge case
|
|
179
|
+
**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.
|
|
128
180
|
|
|
129
181
|
**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.
|
|
130
182
|
|
|
@@ -167,7 +219,7 @@ Four hardcoded Claude model IDs replaced with current strings: `claude-sonnet-4-
|
|
|
167
219
|
|
|
168
220
|
### π Patch: `/subagents list` now shows v4.13+ dispatch agents too
|
|
169
221
|
|
|
170
|
-
**Bug
|
|
222
|
+
**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.
|
|
171
223
|
|
|
172
224
|
**Root cause:** two separate registries for sub-agents:
|
|
173
225
|
- `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)
|
|
@@ -422,7 +474,7 @@ This matches the OpenClaw experience the user was asking about β except it's b
|
|
|
422
474
|
|
|
423
475
|
### π Patch: recover partial output from interrupted background sub-agents
|
|
424
476
|
|
|
425
|
-
**The bug
|
|
477
|
+
**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.
|
|
426
478
|
|
|
427
479
|
**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:
|
|
428
480
|
|
|
@@ -471,7 +523,7 @@ Result: on the next `pollOnce()` after v4.12.4 ships, the three stuck agents get
|
|
|
471
523
|
|
|
472
524
|
### π Patch: Background sub-agent no longer blocks the main Telegram session
|
|
473
525
|
|
|
474
|
-
**The bug
|
|
526
|
+
**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.
|
|
475
527
|
|
|
476
528
|
**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.
|
|
477
529
|
|
|
@@ -693,7 +745,7 @@ Both the platform handler (Slack/Discord/WhatsApp) and the Telegram main handler
|
|
|
693
745
|
|
|
694
746
|
#### P0 #4 β Slack Setup Documentation (`docs/install/slack-setup.md`, `docs/install/slack-manifest.json`)
|
|
695
747
|
|
|
696
|
-
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 (
|
|
748
|
+
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.
|
|
697
749
|
|
|
698
750
|
#### P1 #1 β Slack Progress Ticker (`src/platforms/slack.ts`)
|
|
699
751
|
|
|
@@ -707,7 +759,7 @@ Step-by-step guide: create Slack App from manifest β Socket Mode β App-Level
|
|
|
707
759
|
|
|
708
760
|
`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.
|
|
709
761
|
|
|
710
|
-
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 (`#
|
|
762
|
+
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.
|
|
711
763
|
|
|
712
764
|
#### P1 #3 β Telegram `/workspace` + `/workspaces` Commands
|
|
713
765
|
|
|
@@ -823,7 +875,7 @@ Inspired by Mem0's auto-extraction. When `compactSession()` archives old message
|
|
|
823
875
|
|
|
824
876
|
- **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.
|
|
825
877
|
- **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.
|
|
826
|
-
- **Multi-user isolation deferred.** Memories are still global per data dir. Single-user use case, not a privacy concern for
|
|
878
|
+
- **Multi-user isolation deferred.** Memories are still global per data dir. Single-user use case, not a privacy concern for the maintainer's setup.
|
|
827
879
|
- **Decay/aging deferred.** Daily logs grow monotonically. Will be addressed alongside SQLite migration.
|
|
828
880
|
|
|
829
881
|
#### Testing
|
|
@@ -898,11 +950,11 @@ Live-verified via isolated SDK probe (`node sdk-probe.mjs` inside the repo) whic
|
|
|
898
950
|
|
|
899
951
|
#### What you'll see as a user
|
|
900
952
|
|
|
901
|
-
Send: *"Make a SEO audit of
|
|
953
|
+
Send: *"Make a SEO audit of example.com and example.com in parallel"*
|
|
902
954
|
|
|
903
955
|
- **0 s** β Claude responds: *"Starting both audits in the background β I'll send the reports when done."* Main session **unlocks**.
|
|
904
956
|
- **1β10 min later** β You can chat about anything else. The bot answers immediately.
|
|
905
|
-
- **~13 min** (when each agent finishes) β Two separate banner messages arrive: *"β
SEO audit
|
|
957
|
+
- **~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.
|
|
906
958
|
|
|
907
959
|
#### Non-goals
|
|
908
960
|
|
|
@@ -961,7 +1013,7 @@ He was right. My v4.9.0 `stopWebServer()` fix was *prevention* β it stopped th
|
|
|
961
1013
|
|
|
962
1014
|
### π Two UX bugs found in production after v4.9.2 β now closed
|
|
963
1015
|
|
|
964
|
-
|
|
1016
|
+
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:
|
|
965
1017
|
|
|
966
1018
|
**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.**
|
|
967
1019
|
|
|
@@ -1090,12 +1142,12 @@ The `browse` skill used to instruct the agent to start `node scripts/browse-serv
|
|
|
1090
1142
|
- **Tier 1** β `browser.sh stealth <url>` (Playwright + stealth plugin, headless, Cloudflare-masking)
|
|
1091
1143
|
- **Tier 2** β `browser.sh cdp {start|goto|shot|tabs|stop}` (real Chrome with persistent profile at `~/.claude/hub/BROWSER/profile/`, login cookies survive restarts)
|
|
1092
1144
|
- **Tier 3** β Claude-in-Chrome extension via MCP tools (interactive CLI only)
|
|
1093
|
-
- Explicit escalation ladder (WebFetch β stealth β CDP β ask
|
|
1145
|
+
- Explicit escalation ladder (WebFetch β stealth β CDP β ask the maintainer to log in) and a `NIEMALS browse-server.cjs nutzen` anti-rule.
|
|
1094
1146
|
- Concrete working targets (StepStone β
, Michael Page β
, LinkedIn β
with login, Indeed β) so the agent knows what to try where.
|
|
1095
1147
|
|
|
1096
1148
|
- **`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:
|
|
1097
1149
|
- **`gatewayRequest` now has a 15 s timeout** (`req.destroy` on elapse). Previously a hung gateway would wedge the caller forever.
|
|
1098
|
-
- **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
|
|
1150
|
+
- **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.
|
|
1099
1151
|
- **Explicit PNG extension** on auto-generated screenshot filenames (`shot_<ts>.png`) so Playwright's format inference is unambiguous.
|
|
1100
1152
|
- **Better error messages** β every "needs interactive" throw now includes the exact command to start CDP Chrome (`~/.claude/hub/SCRIPTS/browser.sh cdp start headless`).
|
|
1101
1153
|
|
|
@@ -1124,7 +1176,7 @@ Sub-agents and `ai-query` cron jobs used to hard-cap at 5 minutes (`SUBAGENT_TIM
|
|
|
1124
1176
|
|
|
1125
1177
|
### π Silenced harmless `message is not modified` Telegram errors
|
|
1126
1178
|
|
|
1127
|
-
Occasionally
|
|
1179
|
+
Occasionally the maintainer would see a red banner at the bottom of an Alvin message:
|
|
1128
1180
|
|
|
1129
1181
|
> 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)
|
|
1130
1182
|
|
|
@@ -1159,7 +1211,7 @@ After 4.8.7, running `/update` after a manual rebuild will correctly say *"Disk
|
|
|
1159
1211
|
|
|
1160
1212
|
### β¨ Internal watchdog with crash-loop brake (`src/services/watchdog.ts`)
|
|
1161
1213
|
|
|
1162
|
-
|
|
1214
|
+
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).
|
|
1163
1215
|
|
|
1164
1216
|
**New module**: `src/services/watchdog.ts`. Two responsibilities:
|
|
1165
1217
|
|
|
@@ -1274,7 +1326,7 @@ After 4.8.5, `/update` on the test MacBook will correctly detect the npm install
|
|
|
1274
1326
|
|
|
1275
1327
|
### π WhatsApp self-chat detection for the new `@lid` identity format
|
|
1276
1328
|
|
|
1277
|
-
|
|
1329
|
+
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`.
|
|
1278
1330
|
|
|
1279
1331
|
**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.
|
|
1280
1332
|
|
|
@@ -1346,7 +1398,7 @@ Offline-friendly status command β no running bot required. Prints:
|
|
|
1346
1398
|
- On Linux/Windows: `pm2 jlist` check for the `alvin-bot` process
|
|
1347
1399
|
- **Live info** (when the bot is running with the web UI on :3100): Uptime, active model
|
|
1348
1400
|
|
|
1349
|
-
Answers
|
|
1401
|
+
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.
|
|
1350
1402
|
|
|
1351
1403
|
Example:
|
|
1352
1404
|
|
|
@@ -1449,7 +1501,7 @@ New behavior in `bin/cli.js`:
|
|
|
1449
1501
|
- **pm2 now empty** β *"pm2 now has zero managed processes. Remove it with: `npm uninstall -g pm2`"*
|
|
1450
1502
|
- **pm2 still has other projects** β *"pm2 still has other projects running β leaving it installed."*
|
|
1451
1503
|
|
|
1452
|
-
Caught immediately after 4.7.0 shipped when
|
|
1504
|
+
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.
|
|
1453
1505
|
|
|
1454
1506
|
## [4.7.0] β 2026-04-11
|
|
1455
1507
|
|
|
@@ -1829,7 +1881,7 @@ Remaining unaddressed (by design, require breaking upgrades or overrides):
|
|
|
1829
1881
|
|
|
1830
1882
|
### β¨ Stability Improvements
|
|
1831
1883
|
|
|
1832
|
-
**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
|
|
1884
|
+
**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.
|
|
1833
1885
|
|
|
1834
1886
|
New behavior:
|
|
1835
1887
|
- **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)
|
|
@@ -27,6 +27,7 @@ import { BOT_VERSION } from "../version.js";
|
|
|
27
27
|
import { getWebPort } from "../web/server.js";
|
|
28
28
|
import { getUsageSummary, getAllRateLimits, formatTokens } from "../services/usage-tracker.js";
|
|
29
29
|
import { runUpdate, getAutoUpdate, setAutoUpdate, startAutoUpdateLoop } from "../services/updater.js";
|
|
30
|
+
import { getReleaseHighlights } from "../services/release-highlights.js";
|
|
30
31
|
import { getHealthStatus, isFailedOver } from "../services/heartbeat.js";
|
|
31
32
|
import { t, LOCALE_NAMES, LOCALE_FLAGS } from "../i18n.js";
|
|
32
33
|
// Kick off auto-update loop on module load if the persistent flag is set.
|
|
@@ -1875,6 +1876,17 @@ export function registerCommands(bot) {
|
|
|
1875
1876
|
const result = await runUpdate();
|
|
1876
1877
|
if (result.ok) {
|
|
1877
1878
|
await ctx.reply(`β
${result.message}`);
|
|
1879
|
+
// Extract the installed version from the message (e.g. "Installed v4.16.1 ...")
|
|
1880
|
+
// so we can look up its CHANGELOG block. Falls silently if no match.
|
|
1881
|
+
const versionMatch = result.message.match(/v(\d+\.\d+\.\d+)/);
|
|
1882
|
+
if (versionMatch) {
|
|
1883
|
+
const highlights = getReleaseHighlights(versionMatch[1]);
|
|
1884
|
+
if (highlights) {
|
|
1885
|
+
await ctx.reply(`π *What's new in v${versionMatch[1]}*\n\n${highlights}`, {
|
|
1886
|
+
parse_mode: "Markdown",
|
|
1887
|
+
});
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1878
1890
|
if (result.requiresRestart) {
|
|
1879
1891
|
await ctx.reply(t("bot.update.restarting", lang));
|
|
1880
1892
|
setTimeout(() => process.exit(0), 500);
|
|
@@ -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");
|
|
@@ -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
|