# Telegram Dev Bridge

New workflow code should use `ai/tools/operator-questions/ask.sh` for operator
questions and `ai/tools/operator-daemon/request-action.sh` for registered
local/dev actions. The Telegram checkpoint commands in this directory are now
compatibility plumbing for Telegram transport, details, and legacy tests.

Optional local developer tooling for clean workflow notifications and a small allowlist of repository commands from Telegram.

This bridge is for receiving Dev -> QA lifecycle signals, checking repository state, approving explicit decisions, and running a few registered commands while away from the terminal. It is intentionally outside the application runtime under `ai/tools/telegram` and does not add backend, frontend, Prisma, or GraphQL behavior.

The bridge is a workflow notification and intervention layer, not a raw terminal streaming layer. Telegram messages should be concise, readable on mobile, actionable, low-noise, and stable.

## Quick Start

Validate local configuration without printing the bot token:

```sh
node ai/tools/telegram/telegram-bridge.mjs self-test
```

Run the bridge:

```sh
ai/tools/telegram/bridge.sh poll
```

Recommended remote-operator setup keeps the bridge alive as infrastructure in
one tmux, VS Code devcontainer, or other long-lived shell and Codex/Orchestrator
in another:

```sh
ai/tools/telegram/start-bridge.sh
node ai/runtime/dist/cli.js telegram status --json
```

The Runtime CLI status prints `RUNNING` when the listener has a recent shared
heartbeat, otherwise `NOT_RUNNING`. The listener may run in a different
shell/PID namespace from Codex; a fresh heartbeat and shared state directory are
the cross-session health signal. Remote-autopilot flows should check this before
relying on Telegram replies.

In Telegram, start with:

```text
/help
/status
```

Most operator interactions should come from compact questions that show their
own accepted reply forms. Telegram may only linkify dashed commands up to the
first dash on some clients, so dynamic answers use underscore forms such as
`/yes_<token>` when a question accepts them.

## Human Commands

Normal commands shown by `/help` are intentionally minimal:

- `/status`: runtime stack status.
- `/summary`: concise workflow summary.
- `/pending`: open interactive questions.
- `/timeline`: compact recent action timeline.
- `/help`: short help.

The YAML governance registry is the source of truth for generated operator
help/docs. The TSV registry remains the live dispatch compatibility projection
until the dispatch migration is complete:

```sh
ai/governance/registries/operator-commands.yaml
ai/governance/generators/generate-telegram-help.sh --write
ai/governance/generators/generate-command-docs.sh --write
ai/governance/validators/validate-generated-help.sh
ai/governance/validators/validate-registry-doc-consistency.sh
node ai/runtime/dist/cli.js telegram operator-surface --json
```

Do not add, remove, or rename Telegram commands by editing help text alone.
Update the registry, generated artifacts, handler, docs, and validation
together. After changing Telegram bridge code, generated help, or the command
registry, restart the managed bridge and verify the live surface:

```sh
node ai/runtime/dist/cli.js supervisor request --action telegram_bridge_restart --json
node ai/runtime/dist/cli.js telegram status --json
node ai/runtime/dist/cli.js telegram operator-surface --json
```

Use `/details_<token>` from a question for expanded context about that one
question. Dynamic replies such as yes/no, retry/cancel, fixed custom answers,
numbered answers, and freeform answers are question-specific; they are not
global commands and should not be advertised in global help.

Use `/timeline <run-id|question-id>` to filter recent related events.
`/timeline_full` and `/details_timeline` return a richer mobile-readable
timeline view for debugging. Timeline output is rendered from the canonical
JSONL source through `ai/tools/action-timeline/list.sh`; Telegram should not
show raw JSON by default. The default timeline suppresses heartbeat/debug noise
and deduplicates repeated stale/blocked approval events. Use `--all` from the
shell only when debugging low-level runtime history.

At orchestration run boundaries, Codex sends a short summary with:

```sh
node ai/tools/telegram/send-run-summary.mjs --status finished --summary "..." --problems "none" --details "..." --validation "..." --next "..." --recommendation close
```

Those summaries include `More: /details`. Plain `/details` returns the latest
orchestration-run details using the canonical section order in
`ai/standards/operator-notifications.md`; tokenized `/details_<token>` remains
scoped to a specific question or confirmation.

After a chunk has completion evidence, prefer deriving the Telegram completion
summary from the chunk markdown:

```sh
node ai/tools/telegram/send-run-summary.mjs --from-chunk ai/chunks/active/chunk-000000-example.md --recommendation close --ask-close-commit
```

The compact Telegram message uses the chunk final summary `Good`, `Bad`,
`Ugly`, and `Next` sections. Plain `/details` keeps the richer chunk detail
context from `Details`, `Root Cause`, `Drift Mechanism`, `Policy Enforcement`,
`Validation`, and `Next`.

Use a stable `--run-id` for completion summaries. The helper records summary
delivery and approval creation by run id, so retrying the same run id does not
send another `Orchestration run finished` message or create another approval
question. After the one summary and one approval request are recorded, Codex
should stop; the bridge and runtime stores keep the pending operator state for
later review/resume.

Important runtime, architecture, and governance observations should be sent as
registry-backed notes through:

```sh
ai/tools/operator-notifications/send-event.sh --class runtime_note --title "..." --message "..."
```

Notes are not questions and must not show freeform reply instructions. Only
explicit `freeform_input_required` operator questions may ask for freeform
text.

Runtime Core owns migrated read-only Telegram-facing render semantics. Runtime
API methods can prepare compact Telegram message models, command-surface
summaries, latest details text, and run-summary render models. The bridge still
owns live Telegram transport, long polling, outbox draining, and command
dispatch. Runtime Core must not become a Telegram sender or execution owner.

When a run is ready for human closure/commit review, the same helper may add
`--ask-close-commit` to create a separate yes/no operator question. Approval is
still consumed through the operator-question layer, and close/stage/commit work
still goes through trusted daemon registered actions.

If approval arrives after Codex stopped, `/pending` shows the approved but
unexecuted action. Continue with the approved-action dispatcher
instead of executing the old approval silently.

Legacy and debug commands may remain internally available for compatibility,
tests, or emergency operator workflows, but Telegram should not present them as
the normal public operator interface. Registered workflow actions should be
requested through `ai/tools/operator-daemon/request-action.sh`, not ad hoc
Telegram command menus.

Canonical remote-operator mirroring rules live in
`ai/standards/remote-operator-checkpoints.md`. In short, every local
Codex/Orchestrator human question must be mirrored through Telegram when the
bridge reports `RUNNING`; shell and Telegram answers are alternative inputs to
the same pending checkpoint.

The canonical terminal path for operator questions is
`node ai/tools/telegram/ask-operator.mjs`. Use it instead of ad hoc
`create-checkpoint.mjs` calls when Codex/Orchestrator is about to ask a human
question. The helper creates the Telegram checkpoint before printing the local
question when the bridge is healthy, records whether mirroring succeeded, and
prints `Telegram mirror failed: <reason>` before the local question if
checkpoint creation fails.

This bridge implements the standard with yes/no token replies, numbered
options, fixed answers, constrained text, explicit freeform questions,
`/summary`, `/pending`, and `/details_<token>`.

Telegram is primarily an interactive answer surface. Normal operator help is
intentionally small and only advertises:

```text
/status
/summary
/pending
/timeline
/help
```

Question-specific replies such as `/yes_<token>`, `/retry_<token>`, numbered
answers, fixed answers, and freeform replies are shown only on the question that
accepts them. Legacy/debug commands may remain internally available for tests or
tooling, but they are not part of the normal operator help surface.

## Legacy/Internal Prompt Workflows

Prompt commands are legacy/internal compatibility tooling. They are not part of
the normal `/help` surface and should not be treated as the primary operator
workflow. Normal Codex I/O should use `ai/tools/codex-io-bridge`, operator
decisions should use `ai/tools/operator-questions`, and registered local/dev
actions should use `ai/tools/operator-daemon/request-action.sh`.

The legacy prompt commands prepare text for Codex. Confirmed prompt handoff does
not auto-approve QA results, complete chunks, or commit changes.

Generated prompt content should follow `ai/standards/prompt-synthesis.md`. The standard defines source priority, context limits, stale-state handling, redaction rules, and handoff rules. Prompt synthesis prepares prompts only; `/runqa` and `/rundev` are separate confirmed handoff commands.

The shared terminal helper for generated prompts is:

```sh
ai/commands/prompt-synthesize.sh qa
ai/commands/prompt-synthesize.sh dev
ai/commands/prompt-synthesize.sh dev-fix
ai/commands/prompt-synthesize.sh requirements-review
ai/commands/prompt-synthesize.sh review qa
ai/commands/prompt-synthesize.sh review dev
```

Telegram prompt commands use this helper so Telegram, Orchestrator, and manual workflows do not drift. `/qaprompt` calls `ai/commands/prompt-synthesize.sh qa`; `/devprompt` calls `ai/commands/prompt-synthesize.sh dev`. If the shared helper blocks prompt generation because the canonical state says another action is next, Telegram returns the helper's blocked output instead of inventing separate rules.

`prompt-synthesize.sh <mode>` creates a deterministic draft prompt or a blocked-output message from repository state. `prompt-synthesize.sh review <mode>` creates a Prompt Synthesizer review prompt that can improve or veto that deterministic draft. Telegram can generate and hand off prompts, but AI prompt review is still a separate explicit step until automated orchestration is added.

Generated prompt mode uses fixed repository state. These examples are kept for
legacy/debug operation only:

```text
/qaprompt
```

`/qaprompt` builds and stores the QA prompt returned by `ai/commands/prompt-synthesize.sh qa`. It returns a concise status message by default; use `/lastqaprompt` to retrieve the full prompt for copying.

Legacy handoff example:

```text
/qaprompt
/lastqaprompt
/runqa
/yes_ab12cd34
/nextaction
```

`/lastqaprompt` is optional but recommended when you want to inspect the full generated prompt before submission. `/runqa` creates a confirmation token before submitting anything. The confirmation message shows the target role, active chunk, configured tmux target, prompt source, prompt size, and the next action. Use the actual token returned by your bridge.

```text
/devprompt
```

`/devprompt` builds and stores the Developer prompt returned by `ai/commands/prompt-synthesize.sh dev`. It returns a concise status message by default; use `/lastdevprompt` to retrieve the full prompt for copying.

To hand that prompt to Codex from Telegram:

```text
/devprompt
/lastdevprompt
/rundev
/yes_ab12cd34
/nextaction
```

`/lastdevprompt` is optional but recommended when you want to inspect the full Developer prompt before submission.

Manual prompt mode stores your own text. Use it when you already know the exact prompt:

```text
/qa
<custom prompt>
```

```text
/dev
<custom prompt>
```

The bridge stores the latest prompt in local ignored state. Generated prompt commands are concise by default; retrieve stored full prompts with `/lastqaprompt` or `/lastdevprompt`; clear them with `/clearprompts`.

Manual prompts can use the same handoff:

```text
/qa
Use ai/roles/qa.md.
Review the current active chunk.
```

Then send:

```text
/runqa
/yes_ab12cd34
```

For Developer prompts, use `/dev` followed by multiline content, then `/rundev`.

Prompt handoff requires `tmux` and submits only the stored prompt to `TELEGRAM_CODEX_TMUX_TARGET`. It does not execute arbitrary shell commands and does not accept Telegram text as shell input.

Workflow commands now report the shared helper outputs directly. If QA is needed, the shared helper output points to `ai/commands/prompt-synthesize.sh qa`; if a Developer fix is needed after QA BLOCKED, it points to `ai/commands/prompt-synthesize.sh dev-fix`.

## Messages You May Receive

The bridge may send lifecycle messages such as:

- workflow started
- workflow completed
- workflow failed
- QA PASS
- QA BLOCKED
- validation failed
- runtime smoke failed
- confirmation required
- manual intervention required
- chunk ready for review
- commit ready
- workflow checkpoint decision needed
- explicit user questions

Lifecycle messages should stay compact and show:

- direct status or question.
- short context when needed.
- next actor/action.
- concise reply options or `/details_<token>`.

## Workflow Reports

Legacy workflow report commands are thin wrappers over shared helpers. They may
remain available internally, but normal Telegram help only advertises `/status`,
`/summary`, `/pending`, and `/help`:

- `/workflowstatus`: `ai/commands/workflow-summary.sh --handoff-only`
- `/summary`: `ai/commands/workflow-summary.sh --handoff-only`
- `/lastreport`: `ai/commands/workflow-summary.sh`
- `/nextaction`: `ai/commands/orchestrator-next.sh`
- `/qaprompt`: `ai/commands/prompt-synthesize.sh qa`
- `/devprompt`: `ai/commands/prompt-synthesize.sh dev`

Those helpers derive from repository state only:

- active chunk file metadata
- active chunk `## Execution Notes`
- active chunk `## QA Review`
- active chunk `## Pass History`
- `git status --short --untracked-files=all`
- `git diff --stat`

The bridge does not accept arbitrary file paths for report commands.

Developer handoff uses `## Execution Notes` as the implementation source of truth. QA handoff uses a standard `## QA Review` section with verdict, blockers, runtime smoke decision, validation, cleanup, and recommended next action.

The shared terminal sources for workflow state are:

```sh
ai/commands/workflow-state.sh
ai/commands/orchestrator-next.sh
ai/commands/workflow-summary.sh
ai/commands/prompt-synthesize.sh qa
ai/commands/prompt-synthesize.sh dev
```

Use it to verify readiness before acting on Telegram recommendations:

```sh
ai/commands/workflow-state.sh --ready-for-qa
ai/commands/workflow-state.sh --ready-to-complete
```

Telegram intentionally stays a transport/UI layer over these helpers so terminal, Telegram, Orchestrator, and future automation do not maintain separate workflow interpretation logic.

Expected report patterns:

- Developer finished: chunk is ready for QA; run the QA role against the active chunk.
- QA PASS: request daemon `complete_chunk`, then daemon `git_add_approved`
  and `git_commit` for approved changes.
- QA BLOCKED: send a focused Developer fix prompt for the blocking issues only.
- Manual intervention required: pause automation and ask for human direction before changing scope or bypassing validation.

## Decision Flow

Registered workflow actions should use the trusted daemon. The daemon creates
the operator question and Telegram shows only the accepted answers for that
question.

Example daemon request:

```sh
ai/tools/operator-daemon/request-action.sh --action complete_chunk --target ai/chunks/active/chunk-000012-example.md
```

Telegram receives a compact decision message:

```text
❓ Complete/archive chunk?
Target: ai/chunks/active/chunk-000012-example.md

Reply:
/yes_ab12cd34
/no_ab12cd34

More:
/details_ab12cd34
/summary
/pending
```

The underscore forms are preferred in Telegram because they are one tap-safe
command token. The token is single-use. Expired, reused, or incorrect tokens are
rejected with a compact stale/invalid response. Use `/details_<token>` for
expanded context and `/pending` to list currently valid questions.

`/details_<token>` is scoped to that question or confirmation. It shows the
full question text, accepted answers, daemon action/request id when applicable,
target/chunk, and current state. It must not dump the full workflow summary.

## Ask Operator Helper

Use this helper for every local/platform human question:

```sh
node ai/tools/telegram/ask-operator.mjs --mode yes-no --kind commit-approval --question "Commit staged changes?"
node ai/tools/telegram/ask-operator.mjs --mode numbered --kind qa-choice --question "Choose next action." --options "Fix|Stop|Defer"
node ai/tools/telegram/ask-operator.mjs --mode fixed --kind retry-choice --question "Retry or stop?" --allowed "retry|stop"
node ai/tools/telegram/ask-operator.mjs --mode freeform --kind dev-server-url --question "What frontend URL should I test?"
```

Prefer tap-safe Telegram commands for replies. Yes/no renders as
`/yes_<token>` and `/no_<token>`. Fixed command-safe answers render as
`/retry_<token>`, `/stop_<token>`, or the corresponding allowed answer command.
The bridge also accepts Telegram's bot-suffixed form, such as
`/retry_<token>@BotName`, and records it as the fixed answer instead of showing
generic command help. Numbered answers may remain `1`, `2`, `3`, or exact
option text.

For platform/tool approvals, use this fallback only for unregistered actions
that have no trusted daemon action. Registered actions such as git add, git
commit, complete/archive, dev-server lifecycle, screenshot capture, and runtime
status must use `ai/tools/operator-daemon/request-action.sh`.

```sh
ai/commands/platform-escalation-preflight.sh \
  --target unregistered-tool \
  --platform-action "<exact unregistered command/action>" \
  --reason "no registered daemon action exists for this operation"
```

The local output always starts with one of:

```text
Telegram mirror: created checkpoint ab12cd34
Telegram mirror unavailable: NOT_RUNNING
Telegram mirror failed: <reason>
```

Then it prints the local operator question. Mirror status records are written
under the local Telegram state directory in `ask-operator/`; do not stage that
runtime state.

## Workflow Approval Choke Point

Use the trusted daemon for registered approval-bearing workflow transitions:

```sh
ai/tools/operator-daemon/request-action.sh --action complete_chunk --target ai/chunks/active/<chunk>.md
ai/tools/operator-daemon/request-action.sh --action git_add_approved --files "<reviewed files>"
ai/tools/operator-daemon/request-action.sh --action git_commit --message "<message>"
ai/tools/operator-daemon/wait-result.sh <request-id>
```

`workflow-approve-action.sh` remains a lower-level legacy/manual fallback used
inside registered helpers and for non-daemon workflow approvals.

Use `--approval-mode remote-required` when a remote/autopilot approval must be
driven by Telegram. This mode creates a checkpoint and waits for the Telegram
decision file; piped local stdin is ignored. Use `--approval-mode either` when
shell or Telegram may answer, and `--approval-mode local-only` only for explicit
local fallback.

It calls `ask-operator.mjs` first unless a registered Telegram confirmation path
passes `--preapproved-source`, records the approval under the local Telegram
state directory in `workflow-approvals/`, and prints the approval record path.
Direct `complete-chunk.sh` calls are approval-gated and redirect through this
helper unless they receive a valid approval record.

Git staging and commit are registered daemon actions. Do not request Codex
platform escalation for them.

Telegram text is never executed as shell input.

## Proactive Workflow Checkpoints

For Orchestrator/Chunk Autopilot pauses, use the helper entry point. It checks
that the bridge listener is healthy before relying on remote replies:

```sh
ai/tools/telegram/create-checkpoint.mjs checkpoint completion
ai/tools/telegram/create-checkpoint.mjs checkpoint qa-blocked
ai/tools/telegram/create-checkpoint.mjs checkpoint milestone-stop
ai/tools/telegram/create-checkpoint.mjs checkpoint final-review
```

Lower-level bridge notification mode is still available for tests and manual
debugging:

```sh
node ai/tools/telegram/telegram-bridge.mjs notify-checkpoint completion
node ai/tools/telegram/telegram-bridge.mjs notify-checkpoint qa-blocked
node ai/tools/telegram/telegram-bridge.mjs notify-checkpoint milestone-stop
node ai/tools/telegram/telegram-bridge.mjs notify-checkpoint final-review
```

Default checkpoint notifications are compact and do not include full workflow
summaries or state-machine boilerplate:

```text
❓ decide QA BLOCKED handling

Decision needed: decide QA BLOCKED handling.

Reply:
/yes_ab12cd34
/no_ab12cd34

More:
/details_ab12cd34
/summary
/pending
```

Use `/details_<token>` when the operator needs the active chunk, canonical
state, next chunk, accepted answers, recommendation, or workflow summary excerpt.

Approvals are single-use and bound to the active chunk plus canonical workflow
state that existed when the checkpoint was created. If the chunk or state changes
before `/yes`, the approval is rejected as stale. Telegram checkpoint handling
only runs registered workflow actions. It does not accept arbitrary shell input.

For completion checkpoints, Telegram can run the ready-to-complete gate plus the
registered approval action for the current active chunk. Staging, committing,
and continuing the local Codex/Orchestrator process remain controlled by the
local safe-staging/autopilot policy unless a future helper adds an explicit
registered resume command.

### Custom Operator Questions

For new Codex/Orchestrator human questions, use the Runtime Executor-backed
operator question path. `ask-operator.mjs` and `create-checkpoint.mjs question`
are compatibility facades; the Telegram bridge itself must not create
questions.

```sh
TELEGRAM_CHECKPOINT_QUESTION='Which table strategy should be used?' \
TELEGRAM_CHECKPOINT_OPTIONS='preserve-prime|wrapper-layer|replace-table' \
TELEGRAM_CHECKPOINT_RECOMMENDED='wrapper-layer' \
ai/tools/telegram/create-checkpoint.mjs question table-strategy
```

That command delegates question creation to `runtime.executor.createQuestion`.
The bridge may later deliver the queued Telegram message and consume the reply,
but approval/question semantics stay in Runtime Executor and operator-question
state.

Supported reply modes:

- yes/no: omit options and validation, then reply `yes` or `no`.
- numbered options: set `TELEGRAM_CHECKPOINT_OPTIONS='one|two|three'`, then reply `1`, `2`, `3`, or the option text.
- fixed textual options: set `TELEGRAM_CHECKPOINT_ALLOWED='preserve|replace|skip'`;
  command-safe values render as `/preserve_<token>`, `/replace_<token>`, and
  `/skip_<token>`.
- constrained text: set `TELEGRAM_CHECKPOINT_PATTERN='^[a-z0-9_-]+$'`.
- freeform input: set `TELEGRAM_CHECKPOINT_FREEFORM=true`.

Plain-text replies are accepted only while a custom question checkpoint is
pending. Invalid replies are rejected without consuming the checkpoint. `/summary`
does not consume the checkpoint. Answers are stored as workflow data only and are
never executed as shell input.

Codex/Orchestrator can wait for and consume the local decision file:

```sh
decision_path="$(ai/tools/telegram/wait-for-checkpoint.mjs)"
ai/tools/telegram/consume-checkpoint.mjs "$decision_path"
```

`wait-for-checkpoint.mjs` waits indefinitely by default; pass seconds only when a
work package or operator explicitly configures a timeout. Human questions are
pauses, not terminal stops, and either a local shell answer or a Telegram answer
satisfies the same pending checkpoint when wait mode is used.

Local terminal interaction remains the fallback. If `create-checkpoint.mjs`
reports `NOT_RUNNING`, start the bridge with `start-bridge.sh` or ask/answer the
question in the terminal instead of pretending remote-autopilot is active.

## Debug Event Commands

Lifecycle emitters are for testing and future internal workflow hooks. They are hidden from normal `/help`; use `/helpevents` or `/helpEvents` to list them. The dashed `/help-events` alias still works.

- `/workflow-started`
- `/workflow-completed`
- `/workflow-failed`
- `/validation-failed`
- `/runtime-smoke-failed`
- `/manual-intervention`
- `/chunk-ready`
- `/commit-ready`
- `/qa-pass`
- `/qa-blocked`
- `/notifycheckpoint [kind]`
- `/askcheckpoint [kind]`

These commands are intentionally not positioned as everyday human commands. In the target workflow, users should receive these events from the orchestrated lifecycle rather than needing to send them manually.

## Setup And Environment

Copy the example file and set local values:

```sh
cp ai/tools/telegram/.env.example ai/tools/telegram/.env
```

Supported variables:

- `TELEGRAM_BOT_TOKEN`: required for Telegram polling.
- `TELEGRAM_ALLOWED_CHAT_IDS`: comma-separated chat id allowlist.
- `TELEGRAM_POLL_INTERVAL_MS`: polling delay. Default `2000`.
- `TELEGRAM_COMMAND_TIMEOUT_MS`: command timeout placeholder. Default `600000`.
- `TELEGRAM_CONFIRMATION_TTL_MS`: confirmation token TTL. Default `300000`.
- `TELEGRAM_ENABLE_DEBUG_HTTP`: enables local debug HTTP mode when used by the caller.
- `TELEGRAM_DEBUG_HTTP_PORT`: debug endpoint port. Default `8765`.
- `TELEGRAM_MAX_MESSAGE_LENGTH`: output chunk size. Default `3500`.
- `TELEGRAM_OUTPUT_TAIL_LINES`: default output tail before chunking. Default `80`.
- `TELEGRAM_LOG_COMMAND_CONTEXT`: log non-secret command repo/cwd context for debugging. Default `false`.
- `TELEGRAM_ENABLE_PROGRESS_UPDATES`: progress update toggle. Default `false`.
- `TELEGRAM_CODEX_TMUX_TARGET`: tmux target for `/runqa` and `/rundev`. Default `codex`.
- `TELEGRAM_CODEX_SEND_ENTER`: whether to send Enter after pasting a prompt into tmux. Default `true`.
- `TELEGRAM_STATE_DIR`: optional local bridge state directory. Default `.tmp/telegram-dev-bridge` under the repo root.
- `TELEGRAM_REPO_ROOT`: optional repository root override.
- `TELEGRAM_DEBUG_CHAT_ID`: optional chat id used by `debug-command` and `poll-simulate`.

`bridge.sh` automatically loads `ai/tools/telegram/.env` when it exists. Already-exported environment variables take priority over `.env` values, so you can override one setting for a single command without editing the file:

```sh
TELEGRAM_POLL_INTERVAL_MS=5000 ai/tools/telegram/bridge.sh poll
```

## Polling

Run continuously:

```sh
ai/tools/telegram/bridge.sh poll
ai/tools/telegram/start-bridge.sh
node ai/runtime/dist/cli.js telegram status --json
```

Startup logs include the repository root, mode, allowed chat id count, poll interval, and debug mode status. The raw bot token is never logged.

Recommended runtime location:

- Preferred: run inside the devcontainer when you need full validation commands such as `/validate`.
- macOS host: acceptable for git and chunk inspection commands when `TELEGRAM_REPO_ROOT` points to the same checkout.
- If command output looks empty or unexpected, set `TELEGRAM_LOG_COMMAND_CONTEXT=true` temporarily to log the non-secret repo root and working directory used for command execution.

Poll once:

```sh
node ai/tools/telegram/telegram-bridge.mjs once
```

## Debug Testing

Dispatch a local command without Telegram:

```sh
TELEGRAM_DEBUG_MESSAGE=/help node ai/tools/telegram/telegram-bridge.mjs debug-command
TELEGRAM_DEBUG_MESSAGE=/pending node ai/tools/telegram/telegram-bridge.mjs debug-command
TELEGRAM_DEBUG_MESSAGE=/completechunk node ai/tools/telegram/telegram-bridge.mjs debug-command
TELEGRAM_DEBUG_MESSAGE=/helpevents node ai/tools/telegram/telegram-bridge.mjs debug-command
TELEGRAM_DEBUG_MESSAGE=/status node ai/tools/telegram/telegram-bridge.mjs debug-command
TELEGRAM_ALLOWED_CHAT_IDS=debug node ai/tools/telegram/telegram-bridge.mjs poll-simulate
node ai/tools/telegram/telegram-bridge.mjs self-test
```

`debug-command` uses the first configured allowed chat id by default. Set `TELEGRAM_DEBUG_CHAT_ID` to test a specific chat id.

Start the local-only debug endpoint:

```sh
Legacy debug HTTP was retired with shell dispatch; use `telegram-bridge.mjs debug-command`.
```

Then call it from another shell:

```sh
curl 'http://127.0.0.1:8765/status'
curl 'http://127.0.0.1:8765/diff-stat'
```

The debug endpoint is intentionally simple, localhost-only, and implemented with Python's standard library. Use `debug-command` for deterministic scriptable checks.

## Output Formatting

The bridge strips ANSI escape sequences, carriage-return redraws, common spinner-only lines, and repeated adjacent lines. It does not forward raw streaming output by default.

Normal command replies are grouped into one concise lifecycle message with:

- event type
- what happened
- why it matters
- action needed
- reply options
- a short output block or output tail

Large output is tailed with `TELEGRAM_OUTPUT_TAIL_LINES` before chunking. Use command-level overrides such as `/status --tail 40` or `/validate --tail 120` when a different tail is useful. Full raw output is intentionally not the default behavior and should be reserved for a future explicit debug mode.

The mobile-first expectation is one concise Telegram message per important event. Long command output should be summarized first and tailed/chunked only when needed.

## Safety Model

- Telegram chat ids must be allowlisted.
- Arbitrary shell execution is not supported.
- Commands are registered explicitly in `telegram-bridge.mjs`.
- Mutating commands require single-use confirmation tokens.
- Prompt handoff commands submit only stored prompt files to the configured tmux target.
- Generic command confirmation tokens expire and are stored in local
  memory-backed files under `.tmp/telegram-dev-bridge`.
- Operator approval/question tokens use state/fingerprint validity. They show
  advisory age, but wall-clock age alone must not invalidate an approval; stale
  approvals must name the changed chunk, git, validation, runtime, superseded,
  consumed, replay, or explicit invalidation reason.
- Only one command should be treated as active at a time for this first version.
- Lifecycle event emitters are available for testing/internal hooks, but are intentionally hidden from normal `/help`.

## Troubleshooting

- Run `node ai/tools/telegram/telegram-bridge.mjs self-test` first. If `getMe` fails, check the bot token in `.env`; do not paste the token into logs or commits.
- Confirm `TELEGRAM_ALLOWED_CHAT_IDS` contains the chat id sending messages to the bot. Poll logs report ignored non-allowlisted chat ids.
- Run `TELEGRAM_DEBUG_MESSAGE=/help node ai/tools/telegram/telegram-bridge.mjs debug-command` to verify command dispatch without Telegram.
- Start `ai/tools/telegram/bridge.sh poll` and look for `polling started`, `poll started`, `update received`, `dispatching command`, and `command completed` or `command failed` logs.
- Send `/help` in Telegram to verify command replies before trying mutating commands.

## Limitations

- Telegram update parsing uses Python standard-library JSON parsing when available and falls back to simple parsing for basic text updates.
- Webhooks are not implemented.
- Command concurrency is not implemented.
- Confirmation state is local and not persistent across cleanup of `.tmp`.
- Debug HTTP mode requires `python3`.
- Prompt handoff requires `tmux` and an existing target matching `TELEGRAM_CODEX_TMUX_TARGET`.
- `/tail <lines>` is not implemented yet because there is no safe fixed log source. Future work should add it only for a known bridge-owned log file, not arbitrary file reads or shell commands.
- No arbitrary shell execution is available by design.
