Skip to content

feat(bridges): register CLI-channel config for agents/dispatch-message#130

Merged
chubes4 merged 3 commits into
mainfrom
feat/129-cli-channel-config
May 16, 2026
Merged

feat(bridges): register CLI-channel config for agents/dispatch-message#130
chubes4 merged 3 commits into
mainfrom
feat/129-cli-channel-config

Conversation

@chubes4
Copy link
Copy Markdown
Member

@chubes4 chubes4 commented May 16, 2026

Summary

Each chat bridge installer now writes a CLI-channel config entry consumed by Data Machine Code's generic CLI transport runtime (Extra-Chill/data-machine-code#412), which backs the agents/dispatch-message ability. With this in place, outbound dispatch from a data-machine flow becomes:

agents/dispatch-message
  channel:   'kimaki'              # bridge name
  recipient: '<chat-id>'           # bridge-specific target
  message:   'Time for your check-in.'

…and DMC's CLI runtime shells the configured command. No HTTP, no Python, no nginx hop. This retires the legacy /opt/agent-ping-webhook/ stack from the architecturally-correct side.

Closes #129.

Depends on Extra-Chill/data-machine-code#412. This PR can land independently — the mu-plugin file is harmless on a host without the CLI runtime — but agents/dispatch-message is only functional once DMC#412 ships and a DMC release is deployed.

Mechanism: marker-delimited mu-plugin file

Picked Option A from the issue. Each bridge install/upgrade rewrites its block in:

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

The file registers entries via the datamachine_code_cli_channels filter. Each bridge contributes a marker-delimited block (// BEGIN bridge:<name>// END bridge:<name>) so per-bridge upserts and removals leave other bridges' blocks untouched. Idempotency is verified manually: repeated registers leave the file byte-identical; updating args replaces only the affected block; unregister removes only that block.

Rendered block format:

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

Substitution tokens ({recipient}, {message}) are resolved by DMC#412 at dispatch time.

Per-bridge changes

Bridge Channel name Resolved command argv template recipient semantics
bridges/kimaki.sh kimaki RESOLVED_DATAMACHINE_KIMAKI adapter shim (fallback: KIMAKI_BIN / global kimaki) [send, --channel, {recipient}, --prompt, {message}] Discord channel ID (numeric string)
bridges/cc-connect.sh cc-connect CC_BIN (fallback: PATH cc-connect) [send, --project, {recipient}, {message}] cc-connect project name (platform binding lives in config.toml)
bridges/telegram.sh telegram curl [-sS, -X, POST, https://api.telegram.org/bot<TOKEN>/sendMessage, --data-urlencode, chat_id={recipient}, --data-urlencode, text={message}] Telegram chat ID (numeric, user or group)

Each bridge registers on both bridge_install (setup-time) and bridge_sync_config (upgrade-time) so the resolved command path / token stays fresh across npm-global moves and credential rotations.

Documented assumptions

Two bridges required reading the CLI without being able to run it on this PR's host:

  • cc-connect: cc-connect send is documented in the upstream README (v1.3.x) but the project-routing flag is not explicit. I picked --project {recipient} {message} as a defensible default — a project name is the closest analogue to a routable address in cc-connect's model, since the actual chat platform (Feishu/Slack/Telegram/Discord/…) is bound per-project in config.toml. If --project is unsupported upstream, the argv collapses to [send, {message}] and recipient becomes informational only. This is a fast follow-up — the marker-delimited file makes the swap trivial.
  • telegram: opencode-telegram-bot is inbound-only. It polls Telegram and forwards messages to a local opencode server; it has no send subcommand. To preserve the channel/recipient model anyway, the bridge registers curl against Telegram's sendMessage Bot API with TELEGRAM_BOT_TOKEN captured from either the current env or the bot .env file. The token is baked into the URL fragment of command — rotating the token requires re-running setup/upgrade.

Both assumptions are called out inline in the bridge files and in the README so an operator picking them up later isn't guessing.

Legacy retirement (manual)

Per orchestrator scope-cut: no helper script. The legacy /opt/agent-ping-webhook/ stack exists on exactly one VPS; permanent automation for a one-shot deletion would be technical debt. Retirement is documented as prose + numbered list in README under "Migrating from the legacy agent-ping webhook". Steps cover: reconfigure dispatch flow → systemctl disable --now + remove unit → rm -rf /opt/agent-ping-webhook/ → drop the location /agent-ping/ nginx block → nginx -t && systemctl reload nginx → free port 8422 → remove the token file.

Verification

  • bash -n parses cleanly on lib/cli-channel.sh, all three modified bridges, and setup.sh / upgrade.sh.
  • tests/bridge-render.sh snapshot regressions: passes for everything except a pre-existing kimaki-launchd PATH drift (caused by node being on the test host's PATH — unrelated to this PR; confirmed by re-running the test against main with the changes stashed).
  • Manual end-to-end of lib/cli-channel.sh:
    • Register kimaki on a fresh tmpdir → file created, scaffold + block emitted, php -l clean.
    • Re-register kimaki with identical args → md5sum unchanged (idempotent no-op).
    • Register cc-connect alongside → both blocks present, ordering stable, php -l clean.
    • Re-register kimaki with different timeout → only the kimaki block rewritten, cc-connect untouched.
    • Unregister cc-connect → cc-connect block gone, kimaki block intact, php -l clean.
    • Register telegram with URL containing : and / → escaped correctly in the PHP single-quoted literal, php -l clean.
    • DRY_RUN=true path → logs intent, writes zero bytes.
  • No edits to CHANGELOG.md or VERSION. Conventional commits; homeboy owns the bumps.

Files

  • lib/cli-channel.sh (new): mu-plugin registrar + idempotent block rewriter.
  • setup.sh / upgrade.sh: load the new lib module.
  • bridges/kimaki.sh: _kimaki_register_cli_channel called from install + sync_config.
  • bridges/cc-connect.sh: _cc_connect_register_cli_channel called from install + sync_config.
  • bridges/telegram.sh: _telegram_register_cli_channel called from install + sync_config (with bot .env fallback for token resolution on upgrade).
  • README.md: new "Outbound Dispatch" section + manual migration runbook.

cc <@532385681268408341>

chubes4 added 3 commits May 16, 2026 16:35
Introduce lib/cli-channel.sh, a shared helper that writes a mu-plugin file
at wp-content/mu-plugins/wp-coding-agents-channels.php registering chat
bridges with the Data Machine Code generic CLI transport runtime via the
datamachine_code_cli_channels filter. Bridge install/upgrade calls
cli_channel_register; bridge uninstall calls cli_channel_unregister.
Marker-delimited blocks make multi-bridge rewrites idempotent — per-bridge
upserts and removals do not disturb other bridges' entries.

The mu-plugin file is the discovery surface DMC#412 reads to resolve
agents/dispatch-message channel names to command + argv templates with
{recipient} / {message} substitution tokens.

Wired into setup.sh + upgrade.sh lib loader so bridges/*.sh can call the
helper directly during their install / sync_config hooks.

Refs #129
Depends-on Extra-Chill/data-machine-code#412
Each chat bridge now writes its DMC CLI-channel config entry during install
and upgrade-time sync_config, so the Data Machine Code generic CLI transport
runtime (#412) can route agents/dispatch-message calls without bespoke
webhooks or HTTP hops.

Per bridge:

- kimaki: registers the local datamachine-kimaki adapter shim (falls back
  to the global kimaki binary). recipient = Discord channel ID. argv =
  send --channel {recipient} --prompt {message}.
- cc-connect: registers cc-connect send. recipient = cc-connect project
  name (the platform binding lives in config.toml). argv = send --project
  {recipient} {message}. Assumption to validate upstream: if --project
  isn't supported, the argv collapses to [send,{message}] and recipient
  is informational only.
- telegram: opencode-telegram-bot is inbound-only — no outbound send CLI.
  Registers curl against Telegram's sendMessage Bot API with
  TELEGRAM_BOT_TOKEN baked in at install/upgrade time. recipient =
  Telegram chat ID. Re-running upgrade refreshes the token from the bot
  .env file if rotated.

Refs #129
Depends-on Extra-Chill/data-machine-code#412
Add an 'Outbound Dispatch (agents/dispatch-message)' section to README
covering: how each bridge registers a CLI channel via the mu-plugin
discovery file, the channel/recipient semantics per bridge, and a manual
migration runbook for retiring the legacy /opt/agent-ping-webhook/ stack.

The retirement guidance is intentionally prose + numbered list rather
than an automated helper script. The stack exists on exactly one host
and shipping permanent automation for a one-shot deletion would be
technical debt.

Refs #129
@chubes4 chubes4 merged commit c2bed6b into main May 16, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(bridges): register CLI-channel config for agents/dispatch-message; retire legacy agent-ping webhook

1 participant