DNAlec/dsh-message-push

dsh-message-push

仓库尚未提供可用的公开描述。

Uncategorizedcordis-plugindeepseek-harnessmessage-push

dsh-message-push

English | 中文

A DeepSeek Harness Cordis plugin: every non-subagent session that stops pushes a message to the configured messaging platforms (QQ / Telegram / Feishu / WeChat), so nobody has to watch the screen.

What triggers a push

SituationTriggerText
Task completedturn/end(completed) + agent/status(idle)✅ Task completed
Task interruptedturn/end(aborted)⏸ Task interrupted
Task blockedturn/end(blocked)🚫 Task blocked
Task failedturn/end(error)❌ Task failed (with the error)
Token limitturn/end(max-tokens)↯ Reached token limit
Approval neededapproval/request⚠️ Approval needed
Answer neededuser-questions/request❓ Your answer is needed

Each push carries the session name (title / directory plus the session id tail), a preview of the last reply, and an “open the Web UI” link.

This plugin is the push layer: it does not decide approvals, it does not accept replies as decisions, and it never injects inbound messages into an agent session. Decisions stay in the Web UI; the push only calls you back. Other plugins can call ctx.messagePush.notify({...}) to reuse the same channels and de-duplication.

Install

From GitHub (pinning a tag is steadier):

dsh plugin --profile web add github:DNAlec/dsh-message-push

From a local checkout (development):

git clone https://github.com/DNAlec/dsh-message-push.git
dsh plugin --profile web add "$PWD/dsh-message-push"

pnpm is required. Restart dsh web afterwards — profile composition resolves at startup, and the “Message push” settings page appears only then. Optional dependencies (QQ QR provisioning / the Feishu SDK) are declared in the package and installed by pnpm; a missing one only degrades that channel.

From npm (after a tagged release):

dsh plugin --profile web add @dnalec/dsh-message-push

Uninstall with dsh plugin --profile web remove @dnalec/dsh-message-push. Configuration and credentials under ~/.dsh/message-push/ are not deleted.

Configure

Settings → Message push.

  1. Connect at least one channel (enabled + bound target + connected):
    • QQ — “Scan to create bot” with mobile QQ, or paste AppID / AppSecret. Scanning also binds the scanner as the push target.
    • Telegram — the @BotFather bot token; saving starts long polling.
    • Feishu / Lark — open-platform App ID / Secret (needs the optional @larksuiteoapi/node-sdk).
    • WeChat — “Scan to log in” (official iLink, DMs only; a dedicated account is recommended).
  2. Bind a push target: DM the bot, then click that chat under “Recent incoming”; or, while unbound, reply 是 / yes / ok in a DM and that chat becomes the target.

    This is a private bot design: whoever confirms first owns the push target. Never expose the bot to strangers. QQ groups must @ the bot, and the speaker’s userId must be set.

  3. Send test push — receiving it means the channel works.
  4. Adjust Notification rules: master switch, webUrl, preview length, coalesce window, per-reason toggles, and “push subagent sessions too” (off by default).

Triggering and de-duplication

  • Top-level sessions only by default: sessions with header.origin === 'subagent' or delegationDepth > 0 are skipped.
  • One push per turn: turn/end and agent/status(idle) arrive in no guaranteed order. The first one holds the notification until turn/end supplies the reason (at most reasonUnknownDelayMs, default 8s; on timeout it pushes “Session stopped”).
  • Approval / answer requests win: they are more specific, push immediately, and a following idle folds into the same message (“N more pending in this window”) instead of adding a “task completed” notice.
  • Coalesce window: same-session, same-kind events within repeatWindowSecs (default 60s) merge, re-notifying only when the count changes.
  • A failed push never breaks the host: with no usable channel it writes PUSH_SKIP / PUSH_FAIL to the audit log and warns at most once per 60s.

Service API (for other plugins)

The plugin publishes its service with ctx.provide('messagePush', …) on the host plane:

const push = ctx.get('messagePush')
if (push) {
  push.notify({ kind: 'needs-approval', sessionId, title: 'Fix push plugin', body: 'Approve deleting 3 files' })
}
MethodPurpose
broadcast(text, opts?)Send to every enabled, bound, connected channel → { sent, ok, failed, skipped }
send(channelId, chatId, text, opts?)Send to one channel/target
notify(event)Push through the watcher pipeline (de-duplication + coalescing)
onInbound(fn)Observe inbound text (read-only); return true to claim and stop host handling
channels()Currently pushable channel ids
stateLast push time / result / error

A corrupt config file makes the plugin run on in-memory defaults and never overwrite the disk; the settings page offers an explicit overwrite.

Data

Everything lives in $DSH_HOME/message-push/ (default ~/.dsh/message-push/, mode 0600). Never commit it.

FileContents
config.jsonChannel switches, push targets, notification rules
secrets.jsonQQ AppID/Secret, Telegram token, Feishu AppID/Secret
wechat.jsonWeChat iLink login state
audit.logPUSH / PUSH_FAIL / PUSH_SKIP / BIND / INBOX / CONFIG / WARN

No audit line means this plugin did not push anything.

Development

npm test      # node --test tests/*.test.mjs
npm run check # node --check on every source file

Plain JS, zero @deepseek-ai/* imports: it uses only the cordis injection name timer plus the runtime's fetch / WebSocket. Read AGENTS.md before changing code.

License

MIT