{"_id":"@5dive/langgraph-telegram-hitl","name":"@5dive/langgraph-telegram-hitl","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@5dive/langgraph-telegram-hitl","version":"0.1.0","description":"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).","type":"module","main":"src/index.js","exports":{".":"./src/index.js"},"scripts":{"test":"node --test"},"keywords":["langgraph","langchain","human-in-the-loop","hitl","telegram","interrupt","approval","agent"],"author":{"name":"5dive"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/5dive-ai/langgraph-telegram-hitl.git"},"engines":{"node":">=18"},"peerDependencies":{"@langchain/langgraph":">=0.2.0"},"peerDependenciesMeta":{"@langchain/langgraph":{"optional":true}},"_id":"@5dive/langgraph-telegram-hitl@0.1.0","gitHead":"eb0746f405628a45ed130e9268a2265760e3e163","bugs":{"url":"https://github.com/5dive-ai/langgraph-telegram-hitl/issues"},"homepage":"https://github.com/5dive-ai/langgraph-telegram-hitl#readme","_nodeVersion":"22.22.1","_npmVersion":"10.9.4","dist":{"integrity":"sha512-a/erdx5E0n+3Q6nxaSRg+h1HicYpQ9Q4AsBN0iEQ9lnQBE8IvEXnvqxc2FV6SJsfWsCgffZdQJd/1AlJE10t3A==","shasum":"9b14c255a8ee61e1aa12af341f48dd977a4de8ec","tarball":"https://registry.npmjs.org/@5dive/langgraph-telegram-hitl/-/langgraph-telegram-hitl-0.1.0.tgz","fileCount":6,"unpackedSize":16728,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQChpFCIamYBlFPkHYZma1LE56niC9aF5s80si2i/i+o8wIhAJtpralCbDxlTB7w6YL66QlziNXhOeVuhm6jrL6T/sQ/"}]},"_npmUser":{"name":"lodar","email":"info@5dive.com"},"directories":{},"maintainers":[{"name":"lodar","email":"info@5dive.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/langgraph-telegram-hitl_0.1.0_1783095179651_0.26815009654437705"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-03T16:12:59.463Z","0.1.0":"2026-07-03T16:12:59.857Z","modified":"2026-07-03T16:13:00.116Z"},"maintainers":[{"name":"lodar","email":"info@5dive.com"}],"description":"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).","homepage":"https://github.com/5dive-ai/langgraph-telegram-hitl#readme","keywords":["langgraph","langchain","human-in-the-loop","hitl","telegram","interrupt","approval","agent"],"repository":{"type":"git","url":"git+https://github.com/5dive-ai/langgraph-telegram-hitl.git"},"author":{"name":"5dive"},"bugs":{"url":"https://github.com/5dive-ai/langgraph-telegram-hitl/issues"},"license":"MIT","readme":"# @5dive/langgraph-telegram-hitl\n\nHuman-in-the-loop for [LangGraph.js](https://langchain-ai.github.io/langgraphjs/) over **Telegram**.\n\nLangGraph 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=...)`.\n\n- Zero build step — plain ESM, runs on Node ≥ 18 (uses global `fetch`).\n- Transport is injectable, so the whole flow is unit-testable without a live bot.\n- Interrupt/response shapes follow LangGraph's `HumanInterrupt` / `HumanResponse` conventions, so Agent-Inbox-style graphs work unchanged.\n\n## Install\n\n```bash\nnpm install @5dive/langgraph-telegram-hitl @langchain/langgraph\n```\n\n## Quickstart\n\nYour graph interrupts like any LangGraph HITL graph (a checkpointer is required):\n\n```js\nimport { StateGraph, MessagesAnnotation, MemorySaver, interrupt } from \"@langchain/langgraph\";\nimport { TelegramHITL } from \"@5dive/langgraph-telegram-hitl\";\n\nconst graph = new StateGraph(MessagesAnnotation)\n  .addNode(\"askHuman\", async (state) => {\n    // Pause and ask a human to approve the pending action.\n    const decision = interrupt({\n      action_request: { action: \"send_email\", args: { to: \"ceo@acme.com\", subject: \"Q3\" } },\n      description: \"Send this email?\",\n      config: { allow_accept: true, allow_edit: true, allow_ignore: true },\n    });\n    // `decision` is the HumanResponse the driver resumes with.\n    return { messages: [{ role: \"user\", content: JSON.stringify(decision) }] };\n  })\n  .addEdge(\"__start__\", \"askHuman\")\n  .addEdge(\"askHuman\", \"__end__\")\n  .compile({ checkpointer: new MemorySaver() });\n\nconst hitl = new TelegramHITL({\n  token: process.env.TELEGRAM_BOT_TOKEN,\n  chatId: process.env.TELEGRAM_CHAT_ID,\n});\n\n// Runs the graph to completion, brokering every interrupt through Telegram.\nconst finalState = await hitl.run(graph, { messages: [] }, {\n  configurable: { thread_id: \"email-review-1\" },\n});\n```\n\nWhen the graph interrupts, the chat receives:\n\n```\n🔔 Human input needed\n\nAction: send_email\n{ \"to\": \"ceo@acme.com\", \"subject\": \"Q3\" }\nSend this email?\n\n[ ✅ Approve ]  [ ✏️ Edit ]  [ 🚫 Reject ]\n```\n\n- **Approve** → resumes with `{ type: \"accept\", args: null }`\n- **Reject** → resumes with `{ type: \"ignore\", args: null }`\n- **Edit / Respond** → the bot asks for text; your next chat message becomes `{ type: \"edit\"|\"response\", args: \"<your text>\" }`\n\n## API\n\n### `new TelegramHITL(options)`\n\n| option | type | notes |\n| --- | --- | --- |\n| `token` | string | Telegram bot token (omit if you pass a custom `transport`) |\n| `chatId` | string \\| number | **required** — the chat that approves |\n| `transport` | object | custom transport; defaults to the built-in fetch client |\n| `commandFactory` | `(resume) => Command` | defaults to `new Command({ resume })` from `@langchain/langgraph` |\n| `pollTimeout` | number | `getUpdates` long-poll seconds (default 30) |\n| `onUpdate` | `(chunk) => void` | called with every raw graph stream chunk |\n\n### `hitl.run(graph, input, config)`\n\nStreams 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`).\n\n### Interrupt payload → buttons\n\nThe payload's `config` flags decide which buttons show. With no `config`, all four are offered:\n\n| flag | button | resume `type` |\n| --- | --- | --- |\n| `allow_accept` | ✅ Approve | `accept` |\n| `allow_edit` | ✏️ Edit | `edit` (+ text) |\n| `allow_respond` | 💬 Respond | `response` (+ text) |\n| `allow_ignore` | 🚫 Reject | `ignore` |\n\nA bare string or object interrupt value is rendered as text with all buttons.\n\n### Helpers (exported for custom UIs)\n\n`describeInterrupt(value)`, `allowedActions(value)`, `buildKeyboard(value, token)`, `parseCallback(data, token)`, `TelegramTransport`.\n\n## Testing your own graphs\n\nInject 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`.\n\n## License\n\nMIT © 5dive\n","readmeFilename":"README.md","_rev":"1-7886ea27fe706ad93879c1c7e872317a"}