Add shared project and session selection across IM adapters, preserve bindings on
failed restoration, and synchronize permissions with desktop clients.
Fixes#1286
Since v0.5.1 every IM channel listed only the default project. All five
adapters passed the default work dir to AdapterHttpClient as the sole
allowed project root, so listRecentProjects filtered out everything else;
matchProject, listSessions, sessionExists, createSession and listSkills
were clamped the same way. Feishu is where it was reported, but telegram,
wechat, dingtalk and whatsapp were identical.
defaultWorkDir is documented as where a new IM session starts, not as an
access boundary. Using it as the boundary failed both ways: configured, it
hid every other project; blank, it falls back to PWD/cwd(), which is "/"
for a GUI-launched sidecar, so the boundary allowed the whole filesystem.
Split the two concepts. allowedProjectRoots is now its own setting (global,
per-platform, or ADAPTER_ALLOWED_PROJECT_ROOTS), resolved together with the
work dir by resolveAdapterWorkspace so the default project is always inside
the boundary and /new cannot fail on inconsistent config. The default is the
home directory; it refuses to inherit "/" or any ancestor of home. Pairing
remains the primary authorization control, so unusable roots warn and fall
back rather than locking the bot out.
All five entrypoints now build their client through createAdapterClient
instead of repeating the wiring, which is what let one defect appear in five
places at once.
Known gap, left for a follow-up: a project outside the boundary is still
reported as "not found" rather than "outside the allowed directories".
Found by widening scripts/pr/dead-imports.ts past src/server/ws once its
lexer stopped desyncing. None of these bindings appear anywhere in their own
file; several are the tail of a move that left the import behind, and
useMergedTools still lists replBridgeEnabled in a dependency array after the
useAppState call that set it was stubbed out.
Nine statements lost their last binding. Each target was checked for a
top-level side effect first: node:fs, node:fs/promises and bun:bundle have
none, ws/events.js is a type-only import that never emits, AppState.tsx,
model/model.ts and markdown-style.ts declare only, and utils/config.ts does
register a cleanup but is imported by 157 other files in src, so
attachments.ts dropping it cannot unregister anything.
No behavior change intended and none observed: src is 2995 pass / 37 skip /
30 fail across 273 files before and after, with the 30 failures identical
test for test, and adapters is 437 pass / 0 fail both times. The 30 are
pre-existing and unrelated.
Add a WhatsApp adapter backed by Baileys linked-device auth, plus desktop QR binding UI, server config endpoints, sidecar startup wiring, tests, and documentation.
Constraint: Uses WhatsApp Web linked-device auth, not Meta WhatsApp Business Cloud API.
Tested:
- cd adapters && bun run check:adapters
- bun test src/server/__tests__/adapters.test.ts
- cd desktop && bun run check:desktop
- bun run check:native
- bun run check:persistence-upgrade
- bun run check:docs
Not-tested:
- Live WhatsApp QR pairing, because no WhatsApp account/device was exercised here.
- bun run check:server, because the existing src/server/__tests__/conversations.test.ts timeout still fails independently.
Confidence: medium
Scope-risk: moderate