npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@5dive/langgraph-telegram-hitl

v0.1.0

Published

Human-in-the-loop for LangGraph.js over Telegram — surface interrupt() payloads as approve/reject/edit buttons in a Telegram chat and feed the human's answer back into the graph via Command(resume).

Downloads

33

Readme

@5dive/langgraph-telegram-hitl

Human-in-the-loop for LangGraph.js over Telegram.

LangGraph has native HITL — interrupt() / Command(resume=...) (with a checkpointer) — and an official Slack approval integration, but no Telegram equivalent. This package fills that gap: when your graph hits an interrupt(), the payload is pushed to a Telegram chat as an Approve / Edit / Respond / Reject button prompt. The human taps (or replies with text), and the answer is fed straight back into the graph via Command(resume=...).

  • Zero build step — plain ESM, runs on Node ≥ 18 (uses global fetch).
  • Transport is injectable, so the whole flow is unit-testable without a live bot.
  • Interrupt/response shapes follow LangGraph's HumanInterrupt / HumanResponse conventions, so Agent-Inbox-style graphs work unchanged.

Install

npm install @5dive/langgraph-telegram-hitl @langchain/langgraph

Quickstart

Your graph interrupts like any LangGraph HITL graph (a checkpointer is required):

import { StateGraph, MessagesAnnotation, MemorySaver, interrupt } from "@langchain/langgraph";
import { TelegramHITL } from "@5dive/langgraph-telegram-hitl";

const graph = new StateGraph(MessagesAnnotation)
  .addNode("askHuman", async (state) => {
    // Pause and ask a human to approve the pending action.
    const decision = interrupt({
      action_request: { action: "send_email", args: { to: "[email protected]", subject: "Q3" } },
      description: "Send this email?",
      config: { allow_accept: true, allow_edit: true, allow_ignore: true },
    });
    // `decision` is the HumanResponse the driver resumes with.
    return { messages: [{ role: "user", content: JSON.stringify(decision) }] };
  })
  .addEdge("__start__", "askHuman")
  .addEdge("askHuman", "__end__")
  .compile({ checkpointer: new MemorySaver() });

const hitl = new TelegramHITL({
  token: process.env.TELEGRAM_BOT_TOKEN,
  chatId: process.env.TELEGRAM_CHAT_ID,
});

// Runs the graph to completion, brokering every interrupt through Telegram.
const finalState = await hitl.run(graph, { messages: [] }, {
  configurable: { thread_id: "email-review-1" },
});

When the graph interrupts, the chat receives:

🔔 Human input needed

Action: send_email
{ "to": "[email protected]", "subject": "Q3" }
Send this email?

[ ✅ Approve ]  [ ✏️ Edit ]  [ 🚫 Reject ]
  • Approve → resumes with { type: "accept", args: null }
  • Reject → resumes with { type: "ignore", args: null }
  • Edit / Respond → the bot asks for text; your next chat message becomes { type: "edit"|"response", args: "<your text>" }

API

new TelegramHITL(options)

| option | type | notes | | --- | --- | --- | | token | string | Telegram bot token (omit if you pass a custom transport) | | chatId | string | number | required — the chat that approves | | transport | object | custom transport; defaults to the built-in fetch client | | commandFactory | (resume) => Command | defaults to new Command({ resume }) from @langchain/langgraph | | pollTimeout | number | getUpdates long-poll seconds (default 30) | | onUpdate | (chunk) => void | called with every raw graph stream chunk |

hitl.run(graph, input, config)

Streams the compiled graph, handling each __interrupt__ over Telegram and resuming until the graph completes. A thread_id is generated into config.configurable if absent. Returns the graph's final state (graph.getState).

Interrupt payload → buttons

The payload's config flags decide which buttons show. With no config, all four are offered:

| flag | button | resume type | | --- | --- | --- | | allow_accept | ✅ Approve | accept | | allow_edit | ✏️ Edit | edit (+ text) | | allow_respond | 💬 Respond | response (+ text) | | allow_ignore | 🚫 Reject | ignore |

A bare string or object interrupt value is rendered as text with all buttons.

Helpers (exported for custom UIs)

describeInterrupt(value), allowedActions(value), buildKeyboard(value, token), parseCallback(data, token), TelegramTransport.

Testing your own graphs

Inject a fake transport (records sends, serves scripted getUpdates batches) and a plain commandFactory to drive the whole loop with no network. See test/run.test.js.

License

MIT © 5dive