feat: add Telegram process-event channel - #2933
Conversation
Registers the captain's Telegram channel with the generic process-to-event runner so a captain message wakes firstmate within seconds instead of waiting up to five minutes for a check sweep. The adapter is deliberately thin: it owns Telegram's getUpdates long poll, the write-before-offset invariant that keeps a captain message from being lost, and token handling; ownership, durable capture, publication, and restart recovery stay with bin/fm-procevent.sh. The channel is never terminal on its own - only an explicit retire stops it.
Confidence Score: 5/5The PR appears safe to merge with no concrete unacknowledged defect established in the changed code. The adapter preserves inbox-before-offset persistence, filters unauthorized and non-text updates, keeps credentials out of durable output, and integrates with the existing runner while explicitly documenting its unavoidable pre-capture handoff limitation. Reviews (1): Last reviewed commit: "no-mistakes(document): Polish Telegram c..." | Re-trigger Greptile |
|
Speaking as Kun's firstmate: Scheduled 3:10am PT 8/24 pass. VISION.md read in full from current main VISION (inspected
Class: opt-in. Not default-behavior (must Security (tokens/webhooks, full diff): no Workflow-approval this pass (captain
CI / NM: HEAD Overlap / HOLD: none of the standing spawn/herdr/lock code files. CI is not green yet this pass, so not land-eligible today. Opt-in + matching attestation + no hold-code overlap would be YES once CI is green. Waiting on CI, not the captain. Do not rebase. Land-eligible: NO. Captain-flag NOW: no. |
Intent
Make Telegram a first-class firstmate channel by adding a process-event adapter (bin/fm-procevent-telegram.sh), so the captain's messages wake firstmate within seconds instead of waiting up to five minutes for a check sweep. Inbound Telegram currently runs as a home-local state/telegram-watch.check.sh polled on the watcher's check sweep, so a reply can sit for five minutes. Telegram's getUpdates accepts a timeout parameter and blocks until a message arrives, which is exactly the shape bin/fm-procevent.sh exists to supervise.
Requirements:
-K -stdin config, never as a literal argv element.Tests (tests/fm-procevent-telegram.test.sh) exercise only the executable interface (arm/poll/classify/terminal/retire, plus the real bin/fm-procevent.sh runner for one end-to-end arm+reconcile+capture+publish scenario) - never implementation source bytes. curl is replaced by a fake binary on PATH; no test touches the real Telegram API. Every mandated protection was verified by deliberately removing it and confirming the corresponding test went red, then restoring it:
All five were restored afterward and the suite is green again.
Documentation follows the knowledge-placement decision tree from firstmate-coding-guidelines: exact flags/commands/paths live only in the script's own header and --help. docs/configuration.md and .agents/skills/process-event-sources/SKILL.md each get a minimal pointer (a couple of sentences) naming the adapter, its never-terminal exception, and - for the skill only - how to handle a
procevent telegram telegram Nwake (read state/telegram-inbox/, act on it, reply on Telegram since the captain is away from the desk, move handled files to state/telegram-inbox/handled/, mirroring what the retiring check-sweep script's own header already told firstmate to do) - neither restates the adapter's own contract.Validation performed before this run: bin/fm-lint.sh clean (shellcheck 0.11.0 pinned) on the new/changed files; bin/fm-doc-audience-check.sh clean; the full 161-script firstmate test suite run twice - the only two failures in the second, fully-tooled run are pre-existing and independently confirmed (via a clean git-archive extraction of main) to reproduce identically on main and to be unrelated to this branch: a Pi-follow-up duplicate-captain-answer test in tests/fm-calm-pi-extension.test.sh, and a missing-ruby YAML-parsing failure in tests/fm-test-run.test.sh. bin/fm-procevent-telegram.sh arm was demonstrated end-to-end against the real credential file (registered, shown by bin/fm-procevent.sh list, then retired) with no source left armed afterward.
What Changed
Risk Assessment
✅ Low: The Telegram adapter is well-bounded, the pending-wake recovery now runs before credential validation, prior source-content-only help assertions were removed, and no remaining source-verifiable correctness or intent-conformance defect was found.
Testing
The supplied baseline had already covered lint, documentation checks, and broad tests; this phase ran the focused Telegram suite plus a manual real-runner delivery using fake curl. Delivery produced a durable private inbox message, advanced the shared offset, published and classified the expected wake, preserved the permanent source, and demonstrated safe legacy overlap without duplicate delivery.
Evidence: End-to-end Telegram delivery transcript
Source: End-to-end Telegram delivery transcript
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 1 issue found → auto-fixed (5) ✅
bin/fm-procevent-telegram.sh:319- The required handoff invariant says overlap with the existing check-sweep is "at worst a harmless idempotent redundant write, never lost or duplicated delivery," but this unconditional os.replace rewrites an update-id file even if another poll already delivered it. Concrete sequence: both pollers fetch update 1001; the adapter writes and wakes; Firstmate handles and moves 1001.json to handled/; the overlapping poll then recreates telegram-inbox/1001.json and reports message: 1, causing the same captain message to be delivered and potentially acted on again. Please confirm whether duplicate delivery during handoff is acceptable; otherwise deduplicate atomically at the inbox persistence boundary, including against handled files, before counting the message as newly delivered.🔧 Fix: Prevent duplicate Telegram delivery after handoff
1 error still open:
bin/fm-procevent-telegram.sh:319- The handoff deduplication is not atomic against the retiring check-sweep because only this adapter observes.delivery.lock. Concrete sequence: this poll writes update 1001 and wakes Firstmate; Firstmate moves it tohandled/; then an overlapping invocation of the existing check-sweep, which does not participate in this new lock, rewrites1001.jsonand issues its own wake. The required invariant that overlap must never duplicate delivery therefore remains reachable. The sequential handled-file test does not exercise this competing-writer sequence. Please coordinate retirement before arming, or update both producers to use one shared atomic delivery-claim boundary.🔧 Fix: Require legacy Telegram check retirement before arm
1 error still open:
bin/fm-procevent-telegram.sh:351- A successful inbox write can permanently lose its wake if offset persistence fails afterward. Concrete path: poll writes update 1001,write_offsetfails (for example.telegram-offsetis temporarily a directory), and poll exits nonzero without reporting the message. On retry, 1001 already exists, so it is skipped andmessagesremains 0; the offset then advances and poll again exits nonzero, leaving the captain's message unwoken indefinitely. A crash after offset advancement but before printing has the same outcome. Persist a pending-delivery marker/count before advancing the offset and clear it only after producing a capturable result, so recovery can report already-written but never-announced messages.🔧 Fix: Recover Telegram wakes after offset failures
1 error still open:
bin/fm-procevent-telegram.sh:267- The pending record is deleted before the poll exits and beforefm-procevent.shdurably captures its output, contradicting the requirement to clear it only after printing and exiting 0. Concrete sequence:printfsucceeds,rmdeletes the marker, and the child or runner dies beforefm_procevent_capture; the offset already advanced, so the inbox message is permanently stranded without a wake. Additionally, the unlink is not directory-fsynced, so a power loss can resurrect the marker and duplicate a captured wake. Exact recovery requires an acknowledgement at the runner's durable-capture boundary, or explicit approval to weaken the guarantee because the adapter alone cannot atomically coordinate its exit with its parent.🔧 Fix: Document Telegram pre-capture crash limitations
2 issues (1 error, 1 warning) still open:
bin/fm-procevent-telegram.sh:286- Pending-wake recovery is gated on credentials even though it concerns an already-written message. Concrete sequence: inbox write for update 1001 succeeds, offset persistence fails and leaves.telegram-pending-delivery, then the credential file is removed or becomes unreadable before retry. The retry exits 0 at the credential check without callingreport_pending, and subsequent retries keep producing empty captures while the real message remains unwoken. This contradicts the required recovery behavior that the next poll detect the uncleared marker and report the already-written message. Recover a valid pending record before credential validation or any network setup.tests/fm-procevent-telegram.test.sh:65- The newly added help assertions only search the script's emitted comment text for phrases such asbefore the runner's,power loss, andNever describe. They do not execute or verify any observable crash-recovery behavior and are source-content-only assertions prohibited by the test-quality rule. Remove these phrase checks; the documented limitation is reviewable documentation, while executable tests should assert actual persisted state and outputs.🔧 Fix: Recover pending Telegram wakes without credentials
✅ Re-checked - no issues remain.
🔧 **Test** - 1 issue found → auto-fixed ✅
bin/fm-procevent-telegram.sh:14- The adapter documentation contradicts the authoritative handoff requirement. It says the legacy check-sweep consumer and the new adapter must never overlap and requires deregistration before arm, while the required contract says sharingstate/.telegram-offsetduring the handoff is safe and overlap is at worst an idempotent redundant write. The legacy home-local watcher is absent from this worktree, so that required overlap behavior also could not be demonstrated end to end. Align the header with the required safe-handoff contract and add executable-interface evidence for overlap if the legacy producer can be represented faithfully.bash tests/fm-procevent-telegram.test.shInspectedgit diff 7b88520c055408a18f1476ecce08be60b2885fc9..628abd878acede37c027fb81df6fc8dc45c7cf66to map the change to the acceptance criteria.Checked for the legacystate/telegram-watch.check.sh; it is not present in this isolated worktree, preventing a faithful concurrent-handoff exercise.Verifiedgit status --shortafter testing to confirm no transient worktree artifacts remained.🔧 Fix: Fix Telegram handoff overlap contract
✅ Re-checked - no issues remain.
Inspectedgit diff 7b88520c055408a18f1476ecce08be60b2885fc9..5f57670eb61f7249761369266ee56c97c694fe5fand the Telegram executable-interface regression scenariosbash tests/fm-procevent-telegram.test.shManual fake-Telegram end-to-end run throughbin/fm-procevent-telegram.sh armandbin/fm-procevent.sh reconcile, recording the wake queue, mode-0600 inbox payload, advanced offset, captured result, classification, and still-armed sourceVerified the evidence transcript does not contain the test bot token and checkedgit status --shortfor testing residue✅ **Document** - passed
✅ No issues found.
⏭️ **Lint** - skipped
✅ **Push** - passed
✅ No issues found.