alvin-bot 4.15.2 β 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 +51 -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/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/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,39 @@
|
|
|
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
|
+
|
|
5
38
|
## [4.15.2] β 2026-04-17
|
|
6
39
|
|
|
7
40
|
### π Fix: sleep-aware heartbeat prevents false failover after macOS wake
|
|
@@ -124,7 +157,7 @@ Four hardcoded Claude model IDs replaced with current strings: `claude-sonnet-4-
|
|
|
124
157
|
|
|
125
158
|
### π Patch: watcher zombie-entry fix (missing outputFile > 10 min = failed)
|
|
126
159
|
|
|
127
|
-
**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.
|
|
128
161
|
|
|
129
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.
|
|
130
163
|
|
|
@@ -167,7 +200,7 @@ Four hardcoded Claude model IDs replaced with current strings: `claude-sonnet-4-
|
|
|
167
200
|
|
|
168
201
|
### π Patch: `/subagents list` now shows v4.13+ dispatch agents too
|
|
169
202
|
|
|
170
|
-
**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.
|
|
171
204
|
|
|
172
205
|
**Root cause:** two separate registries for sub-agents:
|
|
173
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)
|
|
@@ -422,7 +455,7 @@ This matches the OpenClaw experience the user was asking about β except it's b
|
|
|
422
455
|
|
|
423
456
|
### π Patch: recover partial output from interrupted background sub-agents
|
|
424
457
|
|
|
425
|
-
**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.
|
|
426
459
|
|
|
427
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:
|
|
428
461
|
|
|
@@ -471,7 +504,7 @@ Result: on the next `pollOnce()` after v4.12.4 ships, the three stuck agents get
|
|
|
471
504
|
|
|
472
505
|
### π Patch: Background sub-agent no longer blocks the main Telegram session
|
|
473
506
|
|
|
474
|
-
**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.
|
|
475
508
|
|
|
476
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.
|
|
477
510
|
|
|
@@ -693,7 +726,7 @@ Both the platform handler (Slack/Discord/WhatsApp) and the Telegram main handler
|
|
|
693
726
|
|
|
694
727
|
#### P0 #4 β Slack Setup Documentation (`docs/install/slack-setup.md`, `docs/install/slack-manifest.json`)
|
|
695
728
|
|
|
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 (
|
|
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.
|
|
697
730
|
|
|
698
731
|
#### P1 #1 β Slack Progress Ticker (`src/platforms/slack.ts`)
|
|
699
732
|
|
|
@@ -707,7 +740,7 @@ Step-by-step guide: create Slack App from manifest β Socket Mode β App-Level
|
|
|
707
740
|
|
|
708
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.
|
|
709
742
|
|
|
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 (`#
|
|
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.
|
|
711
744
|
|
|
712
745
|
#### P1 #3 β Telegram `/workspace` + `/workspaces` Commands
|
|
713
746
|
|
|
@@ -823,7 +856,7 @@ Inspired by Mem0's auto-extraction. When `compactSession()` archives old message
|
|
|
823
856
|
|
|
824
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.
|
|
825
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.
|
|
826
|
-
- **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.
|
|
827
860
|
- **Decay/aging deferred.** Daily logs grow monotonically. Will be addressed alongside SQLite migration.
|
|
828
861
|
|
|
829
862
|
#### Testing
|
|
@@ -898,11 +931,11 @@ Live-verified via isolated SDK probe (`node sdk-probe.mjs` inside the repo) whic
|
|
|
898
931
|
|
|
899
932
|
#### What you'll see as a user
|
|
900
933
|
|
|
901
|
-
Send: *"Make a SEO audit of
|
|
934
|
+
Send: *"Make a SEO audit of example.com and example.com in parallel"*
|
|
902
935
|
|
|
903
936
|
- **0 s** β Claude responds: *"Starting both audits in the background β I'll send the reports when done."* Main session **unlocks**.
|
|
904
937
|
- **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
|
|
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.
|
|
906
939
|
|
|
907
940
|
#### Non-goals
|
|
908
941
|
|
|
@@ -961,7 +994,7 @@ He was right. My v4.9.0 `stopWebServer()` fix was *prevention* β it stopped th
|
|
|
961
994
|
|
|
962
995
|
### π Two UX bugs found in production after v4.9.2 β now closed
|
|
963
996
|
|
|
964
|
-
|
|
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:
|
|
965
998
|
|
|
966
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.**
|
|
967
1000
|
|
|
@@ -1090,12 +1123,12 @@ The `browse` skill used to instruct the agent to start `node scripts/browse-serv
|
|
|
1090
1123
|
- **Tier 1** β `browser.sh stealth <url>` (Playwright + stealth plugin, headless, Cloudflare-masking)
|
|
1091
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)
|
|
1092
1125
|
- **Tier 3** β Claude-in-Chrome extension via MCP tools (interactive CLI only)
|
|
1093
|
-
- 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.
|
|
1094
1127
|
- Concrete working targets (StepStone β
, Michael Page β
, LinkedIn β
with login, Indeed β) so the agent knows what to try where.
|
|
1095
1128
|
|
|
1096
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:
|
|
1097
1130
|
- **`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
|
|
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.
|
|
1099
1132
|
- **Explicit PNG extension** on auto-generated screenshot filenames (`shot_<ts>.png`) so Playwright's format inference is unambiguous.
|
|
1100
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`).
|
|
1101
1134
|
|
|
@@ -1124,7 +1157,7 @@ Sub-agents and `ai-query` cron jobs used to hard-cap at 5 minutes (`SUBAGENT_TIM
|
|
|
1124
1157
|
|
|
1125
1158
|
### π Silenced harmless `message is not modified` Telegram errors
|
|
1126
1159
|
|
|
1127
|
-
Occasionally
|
|
1160
|
+
Occasionally the maintainer would see a red banner at the bottom of an Alvin message:
|
|
1128
1161
|
|
|
1129
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)
|
|
1130
1163
|
|
|
@@ -1159,7 +1192,7 @@ After 4.8.7, running `/update` after a manual rebuild will correctly say *"Disk
|
|
|
1159
1192
|
|
|
1160
1193
|
### β¨ Internal watchdog with crash-loop brake (`src/services/watchdog.ts`)
|
|
1161
1194
|
|
|
1162
|
-
|
|
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).
|
|
1163
1196
|
|
|
1164
1197
|
**New module**: `src/services/watchdog.ts`. Two responsibilities:
|
|
1165
1198
|
|
|
@@ -1274,7 +1307,7 @@ After 4.8.5, `/update` on the test MacBook will correctly detect the npm install
|
|
|
1274
1307
|
|
|
1275
1308
|
### π WhatsApp self-chat detection for the new `@lid` identity format
|
|
1276
1309
|
|
|
1277
|
-
|
|
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`.
|
|
1278
1311
|
|
|
1279
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.
|
|
1280
1313
|
|
|
@@ -1346,7 +1379,7 @@ Offline-friendly status command β no running bot required. Prints:
|
|
|
1346
1379
|
- On Linux/Windows: `pm2 jlist` check for the `alvin-bot` process
|
|
1347
1380
|
- **Live info** (when the bot is running with the web UI on :3100): Uptime, active model
|
|
1348
1381
|
|
|
1349
|
-
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.
|
|
1350
1383
|
|
|
1351
1384
|
Example:
|
|
1352
1385
|
|
|
@@ -1449,7 +1482,7 @@ New behavior in `bin/cli.js`:
|
|
|
1449
1482
|
- **pm2 now empty** β *"pm2 now has zero managed processes. Remove it with: `npm uninstall -g pm2`"*
|
|
1450
1483
|
- **pm2 still has other projects** β *"pm2 still has other projects running β leaving it installed."*
|
|
1451
1484
|
|
|
1452
|
-
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.
|
|
1453
1486
|
|
|
1454
1487
|
## [4.7.0] β 2026-04-11
|
|
1455
1488
|
|
|
@@ -1829,7 +1862,7 @@ Remaining unaddressed (by design, require breaking upgrades or overrides):
|
|
|
1829
1862
|
|
|
1830
1863
|
### β¨ Stability Improvements
|
|
1831
1864
|
|
|
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
|
|
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.
|
|
1833
1866
|
|
|
1834
1867
|
New behavior:
|
|
1835
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");
|
|
@@ -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
|