Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,82 @@ Optional:

Credentials are stored in `~/.config/opencode-telegram-bot/.env` (chmod 600). You can set them as environment variables during setup or edit the file after install.

## Outbound Dispatch (`agents/dispatch-message`)

Each chat bridge installed by wp-coding-agents registers itself as a **CLI channel** with the Data Machine Code generic CLI transport runtime (Extra-Chill/data-machine-code#412). That runtime backs the `agents/dispatch-message` ability, so Data Machine flows can push messages out to whatever platform your bridge speaks — without bespoke webhooks, without HTTP detours, without per-bridge plumbing in DMC itself.

### How it wires up

On install (and every `upgrade.sh` run), each bridge writes its channel config into a mu-plugin file at:

```
$WP_PATH/wp-content/mu-plugins/wp-coding-agents-channels.php
```

The file is owned end-to-end by bridge installers: it is created on first registration, each bridge contributes a marker-delimited block (`// BEGIN bridge:<name>` … `// END bridge:<name>`), and the same install path can rewrite or remove its block idempotently. The file registers entries via the `datamachine_code_cli_channels` filter:

```php
$channels['kimaki'] = [
'command' => '/usr/local/bin/datamachine-kimaki',
'args' => [ 'send', '--channel', '{recipient}', '--prompt', '{message}' ],
'detach' => true,
'timeout' => 600,
];
```

At dispatch time the runtime substitutes `{recipient}` and `{message}` and shells the configured command. No HTTP hop, no nginx detour, no Python.

### Channel names + recipient semantics

| Bridge | Channel name | `recipient` is… | `message` is… |
|-----------------|--------------|----------------------------------------------|------------------------------|
| `bridges/kimaki.sh` | `kimaki` | Discord channel ID (numeric string) | Prompt body delivered to the agent in that channel |
| `bridges/cc-connect.sh` | `cc-connect` | cc-connect project name (from `config.toml`) | Message body sent via `cc-connect send` |
| `bridges/telegram.sh` | `telegram` | Telegram chat ID (numeric, user or group) | Message body posted via Telegram's `sendMessage` Bot API |

A flow step calling the ability looks like:

```
agents/dispatch-message
channel: 'kimaki'
recipient: '1476075959806590989'
message: 'Time for your check-in.'
```

### Bridge-specific notes

- **kimaki** registers the local `datamachine-kimaki` adapter shim wp-coding-agents installs alongside the kimaki binary. The shim normalises Kimaki send flags across versions. If it isn't on disk yet (very early installs), the bridge falls back to the resolved global `kimaki` binary.
- **cc-connect** assumes `cc-connect send` accepts `--project <name> <message>`. cc-connect routes outgoing messages through its currently-bound platform per project (Feishu/DingTalk/Slack/Telegram/Discord/etc.), so `recipient` is the cc-connect project, not a raw chat ID. **Assumption to validate against upstream:** if `--project` is unsupported, the argv collapses to `["send","{message}"]` and `recipient` becomes informational only. Tracked alongside Extra-Chill/wp-coding-agents#129.
- **telegram** is the odd one out. `opencode-telegram-bot` is inbound-only (polls Telegram, forwards to a local opencode server); it has no outbound `send` subcommand. To preserve the channel/recipient model, the bridge registers `curl` against Telegram's `sendMessage` Bot API with `TELEGRAM_BOT_TOKEN` baked in. `recipient` is a Telegram chat ID. The token is captured at install/upgrade time from the existing bot `.env` if not in the current shell — rotating the token requires re-running `upgrade.sh` so the channel config picks up the new value.

### Migrating from the legacy `agent-ping` webhook

Earlier wp-coding-agents installs on Extra Chill's VPS shipped an out-of-band Python webhook at `/opt/agent-ping-webhook/` that POSTed to a local HTTP listener and shelled `kimaki send`. Once the CLI-channel runtime (Extra-Chill/data-machine-code#412) and these bridge registrations are deployed, that stack is dead weight — the same dispatch goes through `agents/dispatch-message` with no HTTP hop.

Retirement is a one-time manual cleanup. There is no helper script: this stack only exists on one host, and shipping permanent automation for a one-shot deletion would be technical debt for an experimental feature. On a host that has the legacy stack:

1. **Reconfigure the dispatch flow first.** Edit the data-machine flow that was POSTing to `https://<site>/agent-ping/` to instead invoke `agents/dispatch-message` with `channel='kimaki'` and `recipient='<discord-channel-id>'`. Run it manually (`wp datamachine flow run <id>`) and confirm the message lands.
2. **Disable and remove the systemd unit.**
```bash
sudo systemctl disable --now agent-ping-webhook.service
sudo rm /etc/systemd/system/agent-ping-webhook.service
sudo systemctl daemon-reload
```
3. **Remove the webhook directory.**
```bash
sudo rm -rf /opt/agent-ping-webhook/
```
4. **Drop the `location /agent-ping/` block from your nginx site config** (e.g. `/etc/nginx/sites-available/<site>`), then:
```bash
sudo nginx -t && sudo systemctl reload nginx
```
5. **Free the local port (8422 in the EC setup) and remove the token file.**
```bash
sudo rm -f /home/opencode/.secrets/agent-ping-token.txt
```

After this, the only outbound message path is `agents/dispatch-message` → DMC CLI runtime → bridge CLI. If you weren't running the legacy webhook to begin with (every install of wp-coding-agents from v0.5.x onward), skip the migration entirely — the bridge registrations land on a clean disk.

## Requirements

**VPS:**
Expand Down
43 changes: 43 additions & 0 deletions bridges/cc-connect.sh
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,43 @@ bridge_install() {
else
_cc_connect_install_systemd
fi

_cc_connect_register_cli_channel
}

# _cc_connect_register_cli_channel
#
# Register cc-connect with the Data Machine Code CLI transport runtime.
#
# `recipient` semantics: cc-connect routes outgoing `send` commands through
# its currently-active project/session as configured in $CC_DATA_DIR/config.toml
# (which can bind multiple platforms — Feishu, DingTalk, Slack, Telegram,
# Discord, etc.). cc-connect's documented `send` subcommand (v1.3.x) takes
# a message body and optional --image/--file flags; it does NOT accept an
# explicit `--channel` / `--recipient` flag — the destination is determined
# by the binding in config.toml. We therefore pass `recipient` to the
# `--project` flag, which is a defensible assumption: a project name is the
# closest analogue to a routable address in cc-connect's model. Operators
# who only run a single project can ignore the value entirely.
#
# Follow-up: validate against cc-connect upstream
# (https://github.com/chenhg5/cc-connect). If `--project` is not supported,
# downgrade the argv to `["send","{message}"]` and document that recipient
# is informational only.
_cc_connect_register_cli_channel() {
local cmd
if [ -n "${CC_BIN:-}" ]; then
cmd="$CC_BIN"
else
cmd="$(command -v cc-connect 2>/dev/null || echo cc-connect)"
fi

cli_channel_register \
"cc-connect" \
"$cmd" \
'["send","--project","{recipient}","{message}"]' \
"true" \
"600"
}

_cc_connect_write_config() {
Expand Down Expand Up @@ -131,6 +168,12 @@ bridge_sync_config() {
fi
log " To update upstream: npm update -g cc-connect"
log " User config (never touched): \$CC_DATA_DIR/config.toml (defaults to \$HOME/.cc-connect/config.toml)"

# Resolve CC_BIN so the channel registration uses the actual path on this host.
if [ -z "${CC_BIN:-}" ]; then
CC_BIN=$(command -v cc-connect 2>/dev/null || echo "cc-connect")
fi
_cc_connect_register_cli_channel
}

# ============================================================================
Expand Down
35 changes: 35 additions & 0 deletions bridges/kimaki.sh
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,37 @@ bridge_install() {
fi

_kimaki_sync_bin_helpers
_kimaki_register_cli_channel
}

# _kimaki_register_cli_channel
#
# Register kimaki with the Data Machine Code CLI transport runtime so that
# `agents/dispatch-message` (substrate: Automattic/agents-api) can deliver
# messages to Discord channels by shelling kimaki. `recipient` is the Discord
# channel ID (numeric string) the message is delivered to. `message` is the
# message body.
#
# We register the local-mode adapter shim (`datamachine-kimaki`) installed by
# _kimaki_sync_bin_helpers; it normalises Kimaki send flags across versions.
# Falls back to the resolved global `kimaki` binary if the adapter isn't on
# disk yet (early VPS installs predating the adapter shim).
_kimaki_register_cli_channel() {
local cmd
if [ -n "${RESOLVED_DATAMACHINE_KIMAKI:-}" ] && [ -x "$RESOLVED_DATAMACHINE_KIMAKI" ]; then
cmd="$RESOLVED_DATAMACHINE_KIMAKI"
elif [ -n "${KIMAKI_BIN:-}" ]; then
cmd="$KIMAKI_BIN"
else
cmd="$(command -v kimaki 2>/dev/null || echo kimaki)"
fi

cli_channel_register \
"kimaki" \
"$cmd" \
'["send","--channel","{recipient}","--prompt","{message}"]' \
"true" \
"600"
}

_kimaki_sync_bin_helpers() {
Expand Down Expand Up @@ -365,6 +396,10 @@ bridge_sync_config() {
fi
fi

# Refresh the CLI-channel registration so DMC's dispatch runtime picks up
# the latest adapter path (npm-global moves between hosts).
_kimaki_register_cli_channel

log " Done."

# Export resolved paths so print_summary can reference them
Expand Down
55 changes: 55 additions & 0 deletions bridges/telegram.sh
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,50 @@ LOG_LEVEL=info"
else
_telegram_install_systemd
fi

_telegram_register_cli_channel
}

# _telegram_register_cli_channel
#
# Register telegram with the Data Machine Code CLI transport runtime.
#
# Mismatch caveat: opencode-telegram-bot is a long-running INBOUND bot — it
# polls Telegram for incoming messages and forwards them to a local opencode
# server. It has no `send` CLI subcommand for pushing outbound messages on
# behalf of agents/dispatch-message. To preserve the channel/recipient
# routing model anyway, this bridge registers `curl` against Telegram's
# sendMessage Bot API, parameterised with the same TELEGRAM_BOT_TOKEN the bot
# already uses. `recipient` is the Telegram chat ID (numeric, e.g. a user ID
# from @userinfobot or a group chat ID). `message` is the message body.
#
# Documented assumptions:
# - TELEGRAM_BOT_TOKEN is set in the environment at register time (it is,
# because bridge_install requires it). The token is baked into the
# `command` URL fragment for simplicity; rotation requires re-running
# setup / upgrade.
# - `curl` is available on the host. Every supported VPS distro ships it
# by default; if absent on a custom box the dispatch will fail loudly,
# which is acceptable for a not-yet-shipped runtime.
# - We do NOT shell out to opencode-telegram-bot for outbound — that bot's
# job is the inbound side, and conflating the two via a single CLI would
# require upstream changes we don't control.
_telegram_register_cli_channel() {
if [ -z "${TELEGRAM_BOT_TOKEN:-}" ]; then
warn " TELEGRAM_BOT_TOKEN not set — skipping CLI-channel registration for telegram"
warn " Re-run setup/upgrade with TELEGRAM_BOT_TOKEN exported once configured"
return 0
fi

local curl_bin
curl_bin=$(command -v curl 2>/dev/null || echo "/usr/bin/curl")

cli_channel_register \
"telegram" \
"$curl_bin" \
"[\"-sS\",\"-X\",\"POST\",\"https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage\",\"--data-urlencode\",\"chat_id={recipient}\",\"--data-urlencode\",\"text={message}\"]" \
"true" \
"60"
}

_telegram_install_launchd() {
Expand Down Expand Up @@ -180,6 +224,17 @@ bridge_sync_config() {
log " User env files (never touched):"
log " \$HOME/.config/opencode-serve.env"
log " \$HOME/.config/opencode-telegram-bot/.env"

# Re-read TELEGRAM_BOT_TOKEN from the bot .env if not in current env; the
# upgrade path typically runs without secrets in env, but the .env file is
# the durable source.
if [ -z "${TELEGRAM_BOT_TOKEN:-}" ]; then
local tg_env="$SERVICE_HOME/.config/opencode-telegram-bot/.env"
if [ -r "$tg_env" ]; then
TELEGRAM_BOT_TOKEN=$(grep -E '^TELEGRAM_BOT_TOKEN=' "$tg_env" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '\r')
fi
fi
_telegram_register_cli_channel
}

# ============================================================================
Expand Down
Loading
Loading