dsh-bluebubbles
其他 活跃维护

dsh-bluebubbles

vINyLogY/dsh-bluebubbles

依托BlueBubbles开源iMessage服务端与DeepSeek大模型,打通iMessage生态与大模型能力,支持消息智能自动回复、内容生成与上下文对话管理,部署简单无复杂配置,原生iMessage使用体验流畅。

0
Stars 标星
0
Forks 分支
0
Watchers 关注
0
Open Issues
TypeScript
主要语言
MIT
开源协议
266 KB
仓库大小
1 个月前
最后推送
一键安装扩展 / 插件指令
dsh plugin --profile web add github:vINyLogY/dsh-bluebubbles
git clone https://github.com/vINyLogY/dsh-bluebubbles.git
git clone git@github.com:vINyLogY/dsh-bluebubbles.git
README.md main

dsh-bluebubbles

Bridges a local BlueBubbles server (the macOS iMessage bridge) into DeepSeek Harness.

Design principle (Unix philosophy): the host plugin keeps only passive capabilities (webhook receive + binding resolution + message injection) and two high-frequency model tools (send text / send attachment); everything else converges into the bb-channel CLI — agents call it via bash, humans and automation scripts use it directly.

Components

Component Location Role
bluebubbles-bridge src/index.ts webhook route + message injection + 2 send tools + the bluebubbles service
dsh-heartbeat src/heartbeat.ts generic periodic wake-up (reads heartbeat-targets.json)
dsh-cron src/cron.ts cron-time tasks (reads cron-jobs.json)
bb-channel bin/bb-channel.mjs CLI: chats/messages/send/bind/contacts/webhook/configure…

Model tools (deliberately only two)

Tool Role
bluebubbles_send_text send a text message
bluebubbles_send_attachment send an attachment (image/file)

Everything else goes through the CLI (agents call it via bash — equivalent capability):

~/.local/bin/bb-channel chats [--limit N] [--all]      # list chats (placeholder/pairing-code noise hidden by default)
~/.local/bin/bb-channel messages <chatGuid> [--limit N] # read history (with sender display names)
~/.local/bin/bb-channel send <chatGuid> <text...>       # send text
~/.local/bin/bb-channel send-attachment <chatGuid> <file>
~/.local/bin/bb-channel attachment <guid> [--dir D]     # download an attachment
~/.local/bin/bb-channel bind <chatGuid> (--workspace PATH | --session ID)
~/.local/bin/bb-channel unbind <chatGuid>
~/.local/bin/bb-channel bindings                        # show the binding table
~/.local/bin/bb-channel contacts / set-contact <address> <name>
~/.local/bin/bb-channel webhook [--url URL]             # check/self-register the webhook
~/.local/bin/bb-channel ping / configure                # connectivity / write ~/.dsh/.env
  • Output is always pretty JSON (jq-friendly); errors go to stderr with exit 1.
  • Credential chain matches the plugin: process.env → ~/.dsh/.env → ~/.zshenv; nothing to fill in by hand.
  • The CLI edits ~/.dsh/bluebubbles-bindings.json / bluebubbles-contacts.json directly; the plugin hot re-reads both files before every inbound message — edits take effect immediately, no reload.

Push path (webhook)

BlueBubbles server (new message)
   │  POST {type:"new-message", data:{...}}
   ▼
DSH webServer route  /bluebubbles/webhook  (loopback only)
   │  hot re-read bindings/contacts → look up chatGuid → workspacePath/sessionId
   ▼
workspace.sessionIds[0] → agents.get(sessionId).send(userMessage, 'next-step', true)
   ▼
The workspace's model wakes up and sees a message annotated
"📱 iMessage · <chat name> · 来自 <name> (<number>)"

Anti-loop (two layers, v22+):

  1. pendingSent queue: the plugin records (chatGuid, normalized text) before every send; webhook echoes with isFromMe=true matching an entry are dropped (60s TTL, unicode NFC-normalized comparison);
  2. seenGuids: after a successful send the real guid returned by the API joins a dedup set (BlueBubbles occasionally pushes the same event twice — second-layer backstop).

A blanket isFromMe drop is not an option: phones on the same Apple ID also produce isFromMe=true in a self-chat DM, and dropping all of them would kill real user messages.

Sender display name: payload.handle.displayName → ~/.dsh/bluebubbles-contacts.json (address→name, maintained via bb-channel set-contact) → bare number.

Relay auto-delivery (same mode for inbound and cron)

With relay: true on a binding, the bridge registers a reply trigger for the woken session (persisted to bluebubbles-relay-state.json, 10min TTL, cleared on turn/end; an exact NO_REPLY reply suppresses delivery). During that turn, every assistant message containing text parts is sent back to the chat immediately — thinking and tool results are never delivered.

When dsh-cron fires a job whose target session has a relay: true binding, it arms the same mechanism through the bluebubbles service's armRelay. Cron task prompts must not tell the model to call send tools itself — that would double-send.

Configuration

Credentials

Method Takes effect
bb-channel configure --password <pw> (writes ~/.dsh/.env) after DSH restart or bridge reload
env var BLUEBUBBLES_PASSWORD (optionally BLUEBUBBLES_BASE_URL) after DSH restart

Never put DSH_-prefixed variables in .env — the DSH bootstrap refuses to start. That is why heartbeat/cron config keys are HEARTBEAT_INTERVAL / HEARTBEAT_TARGETS / CRON_JOBS.

State files ($DSH_HOME, default ~/.dsh)

Path Content Writer
bluebubbles-bindings.json { "chat:<guid>": { workspacePath \| sessionId } } bb-channel bind/unbind
bluebubbles-contacts.json { "address": "display name" } bb-channel set-contact
bluebubbles-media/ inbound attachments (<guid>-<filename>) bridge auto-download
heartbeat-targets.json heartbeat targets hand-edited
cron-jobs.json cron jobs hand-edited

Session resolution chain: sessionId direct → otherwise workspacePath → that workspace's sessionIds[0] (most recent session) → verify a live agent. With no live agent the message is dropped and logged (no fallback, no queue).

Updating the code

  1. Edit src/*.ts → npm run typecheck → git commit
  2. In ~/.dsh/profiles/web/cordis.patch.yml, bump the corresponding row's ?v=N by 1 and save
  3. In-process HMR does not reliably hot-replace the bridge module (stale fiber routes survive), so restarting DSH is the reliable load path; verify the version marker with curl -X POST -d '{}' http://127.0.0.1:3080/bluebubbles/webhook (ok-vN).

Diagnostics

With BLUEBUBBLES_DEBUG=1 (env or .env), inbound events and drop reasons go to ~/.dsh/bluebubbles-debug.log (serialized appends, no lost lines).

Security

  • The webhook route only accepts loopback sources; BlueBubbles webhooks have no signing mechanism.
  • Injected content is plain text messages and triggers no tools; outbound sends always happen through explicit model tool calls.
  • Neither the repository nor the patch file contains credentials.

Tech stack

  • TypeScript (erasable syntax only), types from @deepseek-ai/dsh-* devDeps (0.1.0-rc.7 / cordis 4.0.1).
  • Zero build: Node ≥ 23.6 native type stripping; composition rows point straight at src/index.ts.
  • The CLI is plain Node ESM (bin/bb-channel.mjs), zero dependencies, global fetch/FormData — deliberately .mjs so it runs on any modern Node and stays ESM wherever it is symlinked.
  • Bootstrap: npm install --cache ./.npm-cache && npm run typecheck.