<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Hector Flores</title>
    <description>The latest articles on DEV Community by Hector Flores (@htekdev).</description>
    <link>https://dev.to/htekdev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2155191%2F4eb16de9-82ac-4486-b7cd-6c0ec2b33daf.png</url>
      <title>DEV Community: Hector Flores</title>
      <link>https://dev.to/htekdev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/htekdev"/>
    <language>en</language>
    <item>
      <title>Copilot CLI Weekly: BYOK, Rubber Duck, and Direct Mode Flags</title>
      <dc:creator>Hector Flores</dc:creator>
      <pubDate>Tue, 14 Apr 2026 21:51:23 +0000</pubDate>
      <link>https://dev.to/htekdev/copilot-cli-weekly-byok-rubber-duck-and-direct-mode-flags-35fn</link>
      <guid>https://dev.to/htekdev/copilot-cli-weekly-byok-rubber-duck-and-direct-mode-flags-35fn</guid>
      <description>&lt;h2&gt;
  
  
  The CLI Goes Fully Open: BYOK and Local Models
&lt;/h2&gt;

&lt;p&gt;GitHub dropped a &lt;a href="https://github.blog/changelog/2026-04-07-copilot-cli-now-supports-byok-and-local-models" rel="noopener noreferrer"&gt;changelog entry on April 7&lt;/a&gt; that fundamentally changes what Copilot CLI is: &lt;strong&gt;you can now bring your own model provider or run fully local models&lt;/strong&gt;. No GitHub-hosted routing required. This is BYOK (bring your own key) for the terminal agent, and it's a big deal.&lt;/p&gt;

&lt;p&gt;Here's what it means in practice: you configure environment variables pointing to Azure OpenAI, Anthropic, OpenAI, or any OpenAI-compatible endpoint, and Copilot CLI routes all inference through your provider instead of GitHub's model gateway. This works with remote services and locally running models like Ollama, vLLM, and Foundry Local.&lt;/p&gt;

&lt;p&gt;The kicker? &lt;strong&gt;GitHub authentication is now optional&lt;/strong&gt;. If you're using your own provider, you can start using Copilot CLI with just your model credentials. Sign in to GitHub later if you want features like &lt;code&gt;/delegate&lt;/code&gt;, GitHub Code Search, and the GitHub MCP server. But the core agentic terminal experience works entirely offline and entirely on your infrastructure.&lt;/p&gt;

&lt;p&gt;Set &lt;code&gt;COPILOT_OFFLINE=true&lt;/code&gt; and all telemetry is disabled. Combined with a local model, this enables fully air-gapped development workflows. Run &lt;code&gt;copilot help providers&lt;/code&gt; for setup instructions directly in the terminal. Built-in sub-agents (explore, task, code-review) automatically inherit your provider configuration.&lt;/p&gt;

&lt;p&gt;This isn't just a feature add — it's a positioning move. Copilot CLI is no longer a GitHub-exclusive tool. It's now a provider-agnostic agentic terminal interface that happens to work great with GitHub's infrastructure but doesn't require it. If you're already paying for Azure OpenAI or running local models, you can use the same agent UX without duplicating inference costs.&lt;/p&gt;

&lt;p&gt;I've been saying for months that &lt;a href="https://htek.dev/articles/context-engineering-key-to-ai-development/" rel="noopener noreferrer"&gt;context engineering is the real skill&lt;/a&gt; when working with AI tools. BYOK amplifies this: the CLI's agentic framework (memory, tool routing, sub-agents, MCP integration) is now decoupled from the underlying LLM provider. You control the models. The CLI controls the orchestration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rubber Duck: Cross-Model Reviews Close 75% of the Sonnet-Opus Gap
&lt;/h2&gt;

&lt;p&gt;GitHub &lt;a href="https://github.blog/ai-and-ml/github-copilot/github-copilot-cli-combines-model-families-for-a-second-opinion/" rel="noopener noreferrer"&gt;published a blog post on April 6&lt;/a&gt; introducing &lt;strong&gt;Rubber Duck&lt;/strong&gt;, an experimental feature that uses a second model from a different AI family to review the primary agent's plans and implementations. The results are striking: &lt;strong&gt;Claude Sonnet 4.6 + Rubber Duck running GPT-5.4 closes 74.7% of the performance gap between Sonnet and Opus&lt;/strong&gt; on SWE-Bench Pro, a benchmark of large, difficult, real-world coding problems.&lt;/p&gt;

&lt;p&gt;Here's how it works: when you select a Claude model from the model picker as your orchestrator, Rubber Duck uses GPT-5.4 as an independent reviewer. The job of Rubber Duck is to surface high-value concerns at critical checkpoints — details the primary agent may have missed, assumptions worth questioning, and edge cases to consider. It's not self-reflection (where the same model reviews its own work). It's cross-family review, which catches a different set of errors.&lt;/p&gt;

&lt;p&gt;Rubber Duck activates automatically at three checkpoints:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;After drafting a plan&lt;/strong&gt; — catching suboptimal decisions before they compound downstream&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;After a complex implementation&lt;/strong&gt; — reviewing edge cases in complex code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;After writing tests, before executing them&lt;/strong&gt; — catching gaps in test coverage or flawed assertions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The agent can also invoke Rubber Duck reactively if it gets stuck in a loop or can't make progress. And you can request a critique at any point — Copilot will query Rubber Duck, reason over the feedback, and show you what changed and why.&lt;/p&gt;

&lt;p&gt;The blog post includes real examples of what Rubber Duck catches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Architectural catch&lt;/strong&gt; (OpenLibrary/async scheduler): Rubber Duck caught that the proposed scheduler would start and immediately exit, running zero jobs — and that even if fixed, one of the scheduled tasks was itself an infinite loop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One-liner bug, big impact&lt;/strong&gt; (OpenLibrary/Solr): Rubber Duck caught a loop that silently overwrote the same &lt;code&gt;dict&lt;/code&gt; key on every iteration. Three of four Solr facet categories were being dropped from every search query, with no error thrown.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-file conflict&lt;/strong&gt; (NodeBB/email confirmation): Rubber Duck caught three files that all read from a Redis key which the new code stopped writing. The confirmation UI and cleanup paths would have been silently broken on deploy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rubber Duck is available today in experimental mode via the &lt;code&gt;/experimental&lt;/code&gt; slash command. It's enabled for all Claude family models (Opus, Sonnet, Haiku) as orchestrators, paired with GPT-5.4 as the reviewer. GitHub is exploring other model families for both roles.&lt;/p&gt;

&lt;p&gt;This is a smart implementation. The agent invokes Rubber Duck sparingly, targeting the moments where the signal is highest, without getting in the way. For the technically curious: Rubber Duck is invoked through Copilot's existing &lt;code&gt;task&lt;/code&gt; tool — the same infrastructure used for other sub-agents. It's just another agent in the multi-agent architecture.&lt;/p&gt;

&lt;p&gt;Combined with &lt;a href="https://htek.dev/articles/copilot-cli-weekly-2026-04-03/" rel="noopener noreferrer"&gt;/fleet for parallel multi-agent execution&lt;/a&gt; (shipped April 1), Copilot CLI now supports both horizontal scaling (multiple agents in parallel) and vertical validation (cross-model review). That's a complete multi-agent development platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  v1.0.23: Direct Mode Flags and UX Polish
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/github/copilot-cli/releases/tag/v1.0.23" rel="noopener noreferrer"&gt;Version 1.0.23&lt;/a&gt; shipped on April 10 with one standout feature and a handful of quality-of-life fixes:&lt;/p&gt;

&lt;h3&gt;
  
  
  Direct Agent Mode Flags
&lt;/h3&gt;

&lt;p&gt;The CLI now supports &lt;code&gt;--mode&lt;/code&gt;, &lt;code&gt;--autopilot&lt;/code&gt;, and &lt;code&gt;--plan&lt;/code&gt; flags to &lt;strong&gt;start directly in a specific agent mode&lt;/strong&gt;. No more launching the CLI, waiting for the prompt, then typing &lt;code&gt;/agent&lt;/code&gt; or &lt;code&gt;/autopilot&lt;/code&gt; every time. This is huge for scripting and automation workflows. You can now invoke the CLI programmatically with the exact mode you need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;copilot &lt;span class="nt"&gt;--mode&lt;/span&gt; agent &lt;span class="s2"&gt;"Refactor the auth module"&lt;/span&gt;
copilot &lt;span class="nt"&gt;--autopilot&lt;/span&gt; &lt;span class="s2"&gt;"Run all tests and fix failures"&lt;/span&gt;
copilot &lt;span class="nt"&gt;--plan&lt;/span&gt; &lt;span class="s2"&gt;"Add user preferences feature"&lt;/span&gt;
&lt;span class="sb"&gt;```&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;% endraw %&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;% raw %&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;

This pairs perfectly with BYOK — you can script the CLI against your own model provider with specific agent modes, no interactive input required.

&lt;span class="c"&gt;### Notable Fixes&lt;/span&gt;

- &lt;span class="k"&gt;**&lt;/span&gt;Agent no longer hangs on first turn when memory backend is unavailable&lt;span class="k"&gt;**&lt;/span&gt; — previously, &lt;span class="k"&gt;if &lt;/span&gt;the memory backend failed to connect, the agent would hang indefinitely. Now it degrades gracefully.
- &lt;span class="k"&gt;**&lt;/span&gt;Bazel/Buck build target labels&lt;span class="k"&gt;**&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;e.g. &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;% endraw %&lt;span class="o"&gt;}&lt;/span&gt;//package:target&lt;span class="o"&gt;{&lt;/span&gt;% raw %&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; are no longer misidentified as file paths — &lt;span class="k"&gt;if &lt;/span&gt;you&lt;span class="s1"&gt;'re in a monorepo using Bazel or Buck, this was a real annoyance.
- **Ctrl+L clears the terminal screen without clearing the conversation session** — small UX win for keeping your terminal clean while maintaining context.
- **Slash command picker shows full skill descriptions and a refined scrollbar** — easier to discover and select the right command.
- **`/diff`, `/agent`, `/feedback`, `/ide`, and `/tuikit` work while the agent is running** — you can now interrupt and inspect without stopping the agent entirely.
- **Display reasoning token usage** in the per-model token breakdown when nonzero — useful for tracking extended thinking models.
- **Shell output with BEL characters no longer causes repeated terminal beeping** — if you'&lt;/span&gt;ve ever been subjected to this, you know why it matters.

&lt;span class="c"&gt;## The Bottom Line&lt;/span&gt;

This week&lt;span class="s1"&gt;'s releases signal where Copilot CLI is heading: **provider-agnostic orchestration with cross-model collaboration**. BYOK decouples the CLI from GitHub'&lt;/span&gt;s model gateway. Rubber Duck adds cross-family validation. Direct mode flags make automation workflows seamless.

If you&lt;span class="s1"&gt;'re building [custom Copilot agents](https://htek.dev/articles/top-5-mistakes-creating-custom-github-copilot-agents/), BYOK means you can test against your own model infrastructure. If you'&lt;/span&gt;re worried about cost, BYOK lets you use models you&lt;span class="s1"&gt;'re already paying for. If you'&lt;/span&gt;re &lt;span class="k"&gt;in &lt;/span&gt;an air-gapped environment, offline mode + &lt;span class="nb"&gt;local &lt;/span&gt;models is now a real option.

Rubber Duck is the kind of feature that sounds experimental but will become table stakes. Cross-model review catches a different class of errors than self-reflection. Closing 75% of the Sonnet-Opus gap is not a marginal improvement — it&lt;span class="s1"&gt;'s a structural change in how agents reason about their own work.

The CLI is no longer just a GitHub tool. It'&lt;/span&gt;s an agentic terminal platform that happens to integrate deeply with GitHub when you want it to. That&lt;span class="s1"&gt;'s a much more interesting product.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>github</category>
      <category>devex</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Azure Weekly: Microsoft Declares AI Independence with MAI Models and Agent Framework 1.0</title>
      <dc:creator>Hector Flores</dc:creator>
      <pubDate>Tue, 14 Apr 2026 21:50:45 +0000</pubDate>
      <link>https://dev.to/htekdev/azure-weekly-microsoft-declares-ai-independence-with-mai-models-and-agent-framework-10-54eb</link>
      <guid>https://dev.to/htekdev/azure-weekly-microsoft-declares-ai-independence-with-mai-models-and-agent-framework-10-54eb</guid>
      <description>&lt;p&gt;Liquid syntax error: 'raw' tag was never closed&lt;/p&gt;
</description>
      <category>azure</category>
      <category>ai</category>
      <category>devex</category>
    </item>
    <item>
      <title>GitHub Weekly: Agentic Security, Remote CLI Control, and Code Quality at Scale</title>
      <dc:creator>Hector Flores</dc:creator>
      <pubDate>Tue, 14 Apr 2026 21:50:19 +0000</pubDate>
      <link>https://dev.to/htekdev/github-weekly-agentic-security-remote-cli-control-and-code-quality-at-scale-59ia</link>
      <guid>https://dev.to/htekdev/github-weekly-agentic-security-remote-cli-control-and-code-quality-at-scale-59ia</guid>
      <description>&lt;h2&gt;
  
  
  The Week Security Went Agentic
&lt;/h2&gt;

&lt;p&gt;This week GitHub made three moves that signal where platform security is heading: &lt;a href="https://github.blog/changelog/2026-04-07-dependabot-alerts-are-now-assignable-to-ai-agents-for-remediation" rel="noopener noreferrer"&gt;Dependabot alerts are now assignable to AI agents&lt;/a&gt;, &lt;a href="https://github.blog/changelog/2026-04-07-code-scanning-batch-apply-security-alert-suggestions-on-pull-requests/" rel="noopener noreferrer"&gt;code scanning alerts support batch fixes on PRs&lt;/a&gt;, and &lt;a href="https://github.blog/changelog/2026-04-07-prioritize-security-alerts-with-runtime-context-from-dynatrace" rel="noopener noreferrer"&gt;runtime context from Dynatrace can prioritize alerts&lt;/a&gt; based on deployed artifacts. Separately, &lt;a href="https://github.blog/changelog/2026-04-13-remote-control-cli-sessions-on-web-and-mobile-in-public-preview" rel="noopener noreferrer"&gt;Copilot CLI gained remote control from web and mobile&lt;/a&gt;, and &lt;a href="https://github.blog/changelog/2026-04-14-github-code-quality-improvements-to-standard-findings-in-public-preview/" rel="noopener noreferrer"&gt;GitHub Code Quality got search, bulk operations, and better context&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What ties these together is autonomy. Security remediation is shifting from "here's a list of vulnerabilities" to "here's an agent that can fix them" or "here's exactly which alerts matter in production." The platform is betting that the next layer of security improvement comes from orchestration, not just detection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dependabot Meets Agentic Remediation
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.blog/changelog/2026-04-07-dependabot-alerts-are-now-assignable-to-ai-agents-for-remediation" rel="noopener noreferrer"&gt;Dependabot alerts are now assignable to AI coding agents&lt;/a&gt;—Copilot, Claude, or Codex—for vulnerabilities that need more than a version bump. When a dependency fix requires code changes across your project, you can assign the Dependabot alert to an agent instead of manually tracking down every reference.&lt;/p&gt;

&lt;p&gt;This matters because dependency updates increasingly cascade into breaking API changes. Bumping &lt;code&gt;axios&lt;/code&gt; from 1.x to 2.x might touch 30 files across your codebase. Instead of parsing migration guides and hunting down every instance, you assign the alert to an agent, which analyzes the breaking changes, generates the refactor plan, and opens a PR with the full migration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this is important:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The vulnerability-to-fix pipeline has always been bottlenecked by human capacity. Security teams can identify thousands of issues, but developer time is finite. &lt;a href="https://htek.dev/articles/github-agentic-workflows-hands-on-guide/" rel="noopener noreferrer"&gt;Agentic workflows&lt;/a&gt; flip that constraint—agents can parallelize remediation work across dozens of alerts simultaneously, submitting PRs for review rather than waiting in a backlog.&lt;/p&gt;

&lt;p&gt;If you've been skeptical about AI agents doing "real work," this is the use case that converts skeptics. The scope is well-defined (fix this specific CVE), the validation is clear (tests pass, security scanner clears the alert), and the ROI is measurable (time-to-remediation).&lt;/p&gt;

&lt;h2&gt;
  
  
  Batch Apply Security Fixes on Pull Requests
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.blog/changelog/2026-04-07-code-scanning-batch-apply-security-alert-suggestions-on-pull-requests/" rel="noopener noreferrer"&gt;Code scanning alerts on PRs now support bulk actions&lt;/a&gt;. When GitHub's code scanning surfaces multiple alerts in the Files Changed tab, you can add fixes to a batch and apply them all at once instead of clicking through alerts one at a time.&lt;/p&gt;

&lt;p&gt;This is UX polish that compounds over time. A single PR might trigger 5-10 code scanning alerts for SQL injection risks, XSS vulnerabilities, or hardcoded secrets. Before, you'd click each alert, review the suggestion, apply it, and move to the next. Now you can triage the batch, select the valid fixes, and apply them in one operation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The pattern I expect to see:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Agent opens a PR with new feature work&lt;/li&gt;
&lt;li&gt;Code scanning catches 8 security issues&lt;/li&gt;
&lt;li&gt;Human reviews the batch suggestions in 2 minutes&lt;/li&gt;
&lt;li&gt;Applies all 8 fixes at once&lt;/li&gt;
&lt;li&gt;PR merges&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This reduces the security review bottleneck from "block every PR with issues" to "fix issues in seconds." It's not about skipping security—it's about making the fix so frictionless that blocking becomes acceptable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Runtime Context: Prioritize Alerts That Actually Matter
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.blog/changelog/2026-04-07-prioritize-security-alerts-with-runtime-context-from-dynatrace" rel="noopener noreferrer"&gt;GitHub now integrates runtime context from Dynatrace&lt;/a&gt; to prioritize Advanced Security alerts based on deployed artifacts and runtime risk in your Kubernetes environment. When you connect Dynatrace to GitHub, you see which vulnerabilities are in code paths that are actually executing in production.&lt;/p&gt;

&lt;p&gt;This solves the alert fatigue problem from the other direction. Instead of trying to remediate every CVE in your dependency graph, you focus on the ones that are reachable, running, and exposed to traffic. A vulnerability in a dev-only dependency or dead code path gets deprioritized automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://htek.dev/articles/agentic-devops-next-evolution-of-shift-left/" rel="noopener noreferrer"&gt;shift-left movement&lt;/a&gt; pushed security earlier into the development cycle, but it also created noise. Scanning every commit for every possible vulnerability generates thousands of alerts—most of which don't matter. Runtime context brings shift-right observability back into the equation: "Yes, this CVE exists, but it's in a library we imported and never call."&lt;/p&gt;

&lt;p&gt;The integration pattern here—external observability data feeding back into GitHub's priority scoring—is the template for how platforms should handle alert overload. Don't just generate more alerts. Use runtime data to filter, rank, and route them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Remote Control for CLI Sessions
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.blog/changelog/2026-04-13-remote-control-cli-sessions-on-web-and-mobile-in-public-preview" rel="noopener noreferrer"&gt;Copilot CLI now supports &lt;code&gt;copilot --remote&lt;/code&gt;&lt;/a&gt;, letting you monitor and steer a running CLI session directly from GitHub on the web or mobile. You start a long-running task on your machine—database migration, test suite, deployment—and can check progress, send input, or intervene from your phone while away from your terminal.&lt;/p&gt;

&lt;p&gt;This is the natural next step after &lt;a href="https://htek.dev/articles/copilot-cli-biggest-week-yet/" rel="noopener noreferrer"&gt;Copilot CLI gained agentic capabilities&lt;/a&gt;. Agents run in the background, but you still need visibility and control. Remote sessions give you that without SSHing into your machine or opening a VNC viewer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use cases I expect to see:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start a deployment script, monitor from mobile, approve production gate from phone&lt;/li&gt;
&lt;li&gt;Kick off multi-hour integration tests before leaving office, check results on the train&lt;/li&gt;
&lt;li&gt;Let an agent run exploratory work overnight, review the session transcript the next morning&lt;/li&gt;
&lt;li&gt;Handoff work to a teammate by sharing a remote session link instead of screen-sharing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The public preview is available now—no additional setup beyond updating Copilot CLI. Sessions are authenticated via your GitHub account and encrypted in transit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Quality Gets Search and Bulk Operations
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.blog/changelog/2026-04-14-github-code-quality-improvements-to-standard-findings-in-public-preview/" rel="noopener noreferrer"&gt;GitHub Code Quality standard findings&lt;/a&gt; now support:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Search by file path&lt;/strong&gt;: Filter findings to specific files instead of scrolling through hundreds of results&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bulk dismiss/reopen&lt;/strong&gt;: Select multiple findings and dismiss or reopen them in bulk from the rule page&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full context per finding&lt;/strong&gt;: See diagnostic messages and related code locations instead of just the line number&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are table-stakes features for any code analysis tool, but GitHub is iterating fast. Code Quality launched in public preview recently, and they're shipping improvements weekly. The baseline experience is "scan your default branch, surface reliability and maintainability issues, generate Copilot Autofix suggestions for each finding."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where this is heading:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Code Quality will likely converge with code scanning and Dependabot into a unified "things wrong with your codebase" dashboard. Right now, security issues live in one tab, dependency alerts in another, and code quality findings in a third. The logical endpoint is a single prioritized work queue—sorted by runtime risk, business impact, and fix effort—that routes work to agents or humans based on complexity.&lt;/p&gt;

&lt;p&gt;If GitHub executes on that vision, it becomes the system of record for technical debt remediation, not just vulnerability management.&lt;/p&gt;

&lt;h2&gt;
  
  
  Actions Workflows Limited to 50 Reruns
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.blog/changelog/2026-04-10-actions-workflows-are-limited-to-50-reruns/" rel="noopener noreferrer"&gt;GitHub Actions workflows are now limited to 50 reruns&lt;/a&gt;. If you attempt to rerun a workflow more than 50 times, you'll get a failed check suite with an annotation indicating you hit the limit.&lt;/p&gt;

&lt;p&gt;This is an operational guardrail, not a product change. The pattern they're preventing: flaky tests that fail 49 times before someone finally gets a green run and merges the PR. Instead of letting workflows run indefinitely, GitHub is forcing you to fix the underlying flakiness or re-trigger the workflow from scratch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why 50?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's the threshold where "temporary CI infrastructure issue" becomes "your tests are fundamentally broken." One or two reruns for a flaky network call is reasonable. Fifty reruns means you have a systemic problem—race conditions, non-deterministic tests, or environment-specific failures—that rerunning won't fix.&lt;/p&gt;

&lt;p&gt;The limit includes both manual reruns and automated retries. If you've built CI retry logic that hammers the same workflow run, you'll need to adjust it to trigger fresh workflow runs instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Week Signals
&lt;/h2&gt;

&lt;p&gt;GitHub is building the infrastructure layer for agentic DevOps. Dependabot alerts route to agents. Code scanning fixes batch-apply. Runtime context filters noise. CLI sessions detach from terminals. Code Quality surfaces work queues.&lt;/p&gt;

&lt;p&gt;None of these changes are revolutionary on their own, but the pattern is clear: shift work from "human clicks through UI to make change" to "agent operates autonomously with human approval gates." The platform is preparing for a world where most security remediation, dependency updates, and code quality fixes are handled by agents, and humans focus on architecture, prioritization, and review.&lt;/p&gt;

&lt;p&gt;If you're building &lt;a href="https://htek.dev/articles/github-agentic-workflows-hands-on-guide/" rel="noopener noreferrer"&gt;agentic workflows&lt;/a&gt; today, GitHub is meeting you halfway. The platform now supports assigning work to agents, applying fixes in bulk, and prioritizing based on production impact. What's left is wiring those capabilities into your specific toolchain—assigning Dependabot alerts to your internal agents, connecting your observability platform for runtime context, and orchestrating batch fixes through your review process.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;This week GitHub made security remediation agentic, added remote control to CLI sessions, and improved Code Quality's usability. The underlying theme: shift repetitive security work to agents, give humans better prioritization signals, and remove friction from the fix-review-merge loop.&lt;/p&gt;

&lt;p&gt;If you're still doing dependency updates by hand, this is the week to reconsider. The tooling is maturing fast, and the gap between "manual remediation" and "agent-assisted remediation" is widening. Not because agents are perfect—they're not—but because the platform is optimizing for agent-first workflows, and manual processes are becoming the slow path.&lt;/p&gt;

</description>
      <category>github</category>
      <category>devops</category>
      <category>devex</category>
      <category>ai</category>
    </item>
    <item>
      <title>I Open-Sourced the AI That Runs My Household</title>
      <dc:creator>Hector Flores</dc:creator>
      <pubDate>Tue, 14 Apr 2026 21:49:55 +0000</pubDate>
      <link>https://dev.to/htekdev/i-open-sourced-the-ai-that-runs-my-household-57ep</link>
      <guid>https://dev.to/htekdev/i-open-sourced-the-ai-that-runs-my-household-57ep</guid>
      <description>&lt;h2&gt;
  
  
  The System That Knows When to Buy Dog Food
&lt;/h2&gt;

&lt;p&gt;Last Tuesday, my phone buzzed at 5:03 AM with a morning briefing: weather, three calendar events, two overdue tasks, a bill due Friday, and a reminder that the HVAC filter was overdue for replacement. I hadn't asked for any of it. By 6:15, I'd knocked out the first task — refill a prescription — and the system automatically served me the next one.&lt;/p&gt;

&lt;p&gt;That's not a productivity app. It's not Alexa with extra steps. It's a &lt;strong&gt;multi-agent AI system&lt;/strong&gt; running on &lt;a href="https://docs.github.com/en/copilot/github-copilot-in-the-cli" rel="noopener noreferrer"&gt;GitHub Copilot CLI&lt;/a&gt;, communicating through Telegram, managing everything from groceries to OB appointments to content scheduling — and I just &lt;a href="https://github.com/htekdev/copilot-home-assistant" rel="noopener noreferrer"&gt;open-sourced the whole thing&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How We Got Here
&lt;/h2&gt;

&lt;p&gt;It started with a &lt;a href="https://htek.dev/articles/who-needs-openclaw-copilot-cli-extensions/" rel="noopener noreferrer"&gt;single-file Telegram bridge&lt;/a&gt; — a few hundred lines of JavaScript that let me chat with Copilot CLI from my phone. Then I added a &lt;a href="https://htek.dev/articles/safe-openclaw-cron-iac-openshell/" rel="noopener noreferrer"&gt;cron scheduler and OpenShell sandboxing&lt;/a&gt;. Then meal planning. Then budget tracking. Then a task coach that understood my ADD brain needed tasks served one at a time, not dumped in a list.&lt;/p&gt;

&lt;p&gt;Three months later, I have a system with &lt;strong&gt;17 specialized agents&lt;/strong&gt;, &lt;strong&gt;16 custom extensions&lt;/strong&gt;, and &lt;strong&gt;15 scheduled cron jobs&lt;/strong&gt; running autonomously. My wife gets pregnancy reminders. My 4-year-old has a curriculum tracker. The dogs' feeding schedule is logged. The house maintenance runs on a schedule I never have to think about.&lt;/p&gt;

&lt;p&gt;It's not a demo. It's real — it's been managing our household since January, and every correction we make gets persisted permanently so the system never repeats a mistake.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture: Agents Are Markdown, Extensions Are Code
&lt;/h2&gt;

&lt;p&gt;The entire system sits in a single Git repository with a clean separation between &lt;em&gt;behavior&lt;/em&gt; (what agents should do) and &lt;em&gt;capability&lt;/em&gt; (what they can do).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agents&lt;/strong&gt; are Markdown files in &lt;code&gt;.github/agents/&lt;/code&gt;. Each one defines an AI persona with domain expertise, decision rules, memory loading, and integration points. The &lt;code&gt;finance-manager&lt;/code&gt; knows how to categorize expenses and track bill due dates. The &lt;code&gt;dog-parent&lt;/code&gt; tracks feeding schedules and vet appointments. The &lt;code&gt;task-coach&lt;/code&gt; serves tasks one at a time with momentum tracking — because dumping 30 tasks on someone with ADD is the same as giving them zero tasks.&lt;/p&gt;

&lt;p&gt;Here's what an actual agent definition looks like — this is from the &lt;code&gt;task-coach&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Task Coach — Your Family Productivity Partner&lt;/span&gt;

&lt;span class="gu"&gt;## Identity &amp;amp; Personality&lt;/span&gt;

You are the family's productivity coach — you nudge BOTH parents.
You're energetic, encouraging, and no-nonsense. You understand that
long lists are paralyzing, context switches are expensive, and
momentum is everything.

&lt;span class="gu"&gt;### For {YourName} (ADD-friendly coaching):&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**One task at a time.**&lt;/span&gt; Never dump a list. Always serve the single next thing.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Celebrate wins.**&lt;/span&gt; Every completion gets a brief cheer before the next task.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Track momentum.**&lt;/span&gt; "You've knocked out 4 tasks today! Keep the streak going."

&lt;span class="gu"&gt;### For {Spouse} (gentle pregnancy-friendly coaching):&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Ultra-short messages.**&lt;/span&gt; 2-3 lines max. She's carrying twins.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Don't nag.**&lt;/span&gt; If no response, wait 2+ hours. She may be resting.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's not pseudocode — that's the actual agent file. The &lt;code&gt;{YourName}&lt;/code&gt; and &lt;code&gt;{Spouse}&lt;/code&gt; placeholders are what you replace when you fork the repo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Extensions&lt;/strong&gt; are Node.js ESM modules in &lt;code&gt;.github/extensions/&lt;/code&gt;. They expose tools like &lt;code&gt;add_expense&lt;/code&gt;, &lt;code&gt;set_meal&lt;/code&gt;, &lt;code&gt;telegram_send_message&lt;/code&gt;, and &lt;code&gt;gcal_create_event&lt;/code&gt;. The Copilot CLI &lt;a href="https://htek.dev/articles/github-copilot-cli-extensions-complete-guide/" rel="noopener noreferrer"&gt;extension SDK&lt;/a&gt; lets you create these with the &lt;code&gt;joinSession&lt;/code&gt; API — hooks intercept agent actions in real time.&lt;/p&gt;

&lt;p&gt;Here's the &lt;code&gt;auto-commit&lt;/code&gt; extension — it watches for data changes and auto-saves to Git after every tool call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// .github/extensions/auto-commit/extension.mjs&lt;/span&gt;

&lt;span class="c1"&gt;// Tools that modify family data files&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FILE_MODIFY_TOOLS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;add_task&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;update_task&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;complete_task&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;add_to_shopping_list&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;check_off_item&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;set_meal&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;add_recipe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;add_expense&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;add_income&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;set_budget&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;add_maintenance_task&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;log_maintenance&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;joinSession&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="na"&gt;hooks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;onPostToolUse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// After any data-modifying tool, commit and push&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;FILE_MODIFY_TOOLS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toolName&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;execSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;git add -A &amp;amp;&amp;amp; git commit -m 'Auto-save'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="nf"&gt;execSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;git push origin main&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;additionalContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[auto-commit] ✅ Committed and pushed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every time an agent logs an expense, completes a task, or sets a meal — the data is automatically committed to Git. No manual saves. No data loss.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The constitution&lt;/strong&gt; (&lt;code&gt;data/constitution.md&lt;/code&gt;) governs every agent. It defines autonomy levels, communication rules, and the core principles that make this work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Core Principles&lt;/span&gt;
&lt;span class="p"&gt;
1.&lt;/span&gt; &lt;span class="gs"&gt;**Act first, report after.**&lt;/span&gt; You are autonomous. Detect → act → notify.
   Never say "would you like me to...?" — just do it and tell them what you did.
&lt;span class="p"&gt;2.&lt;/span&gt; &lt;span class="gs"&gt;**Be specific and actionable.**&lt;/span&gt;
   ✅ "Call MOHELA today — 90 days delinquent. Phone: 1-800-848-0979"
   ❌ "You might want to look into your MOHELA situation."
&lt;span class="p"&gt;3.&lt;/span&gt; &lt;span class="gs"&gt;**Every correction is permanent.**&lt;/span&gt; When the family corrects you,
   persist the lesson via store_memory, standing-orders.md,
   AND copilot-instructions.md. Never repeat the same mistake.

&lt;span class="gu"&gt;## Autonomy Levels&lt;/span&gt;

| Do it immediately              | Ask first                       |
|--------------------------------|---------------------------------|
| Create calendar events &amp;amp; tasks | Major purchases (&amp;gt;$200)         |
| Add to shopping lists          | Medical decisions               |
| Relay messages between family  | Sending emails on someone's behalf |
| Log expenses, create bills     | Deleting any data               |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every agent reads this constitution before doing anything else. It's the shared brain that keeps 17 agents behaving consistently.&lt;/p&gt;

&lt;p&gt;Here's the actual architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Telegram ↔ Copilot CLI ↔ Agents (17) + Extensions (16) + Cron (15)
              ↕
    Google Calendar / Gmail / Maps / Tasks
    Late/Zernio (social media scheduling)
    Budget database / Shopping lists / Recipes
    Family profiles / Home maintenance / Locations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What It Actually Manages
&lt;/h2&gt;

&lt;p&gt;This isn't a toy. Here's what runs every day:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Morning briefings&lt;/strong&gt; hit Telegram at 5 AM weekdays, 7 AM weekends. Weather, today's calendar, prioritized tasks, unread email summary, today's meals, upcoming bills, and health reminders — all compiled by the &lt;code&gt;daily-briefing&lt;/code&gt; agent delegating to domain specialists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Task coaching&lt;/strong&gt; runs every 20 minutes. The &lt;code&gt;task-coach&lt;/code&gt; checks what's pending, evaluates priorities and dependencies, and nudges with one task at a time. It knows to chain errands by location, front-load deadlines, and serve quick wins first for momentum. Our record was 18 tasks completed in one day — not because we were heroic, but because the system served them in the right order.&lt;/p&gt;

&lt;p&gt;All of this is driven by &lt;code&gt;cron.json&lt;/code&gt; — here's a slice of the actual schedule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timezone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"America/Chicago"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"jobs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"morning-briefing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"schedule"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0 5 * * 1-5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"agent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"daily-briefing"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"task-coach-nudge"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"schedule"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*/20 5-22 * * *"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"agent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"task-coach"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"heartbeat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"schedule"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*/30 5-22 * * *"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"agent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"checkin"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nightly-reflection"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"schedule"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0 21 * * *"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"agent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"platform-manager"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fifteen jobs in total. Add an agent, add a cron entry — it starts running on the next cycle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Meal planning&lt;/strong&gt; happens Saturday mornings. The &lt;code&gt;nutrition-chef&lt;/code&gt; manages three dietary tracks (I'm on a high-protein cut, my wife has pregnancy dietary needs, and we have a picky 4-year-old). The extension generates grocery lists cross-referenced with recipes and sends them to the shopping list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Budget tracking&lt;/strong&gt; is continuous. Every expense gets logged, categorized, and compared against monthly targets. The &lt;code&gt;finance-manager&lt;/code&gt; scans Gmail for bills, creates tasks for upcoming payments, and the &lt;code&gt;budget-review&lt;/code&gt; agent delivers a full monthly report on the 1st.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Home maintenance&lt;/strong&gt; runs on schedules — HVAC filters, water heater flushes, pest control, appliance checks. The &lt;code&gt;home-manager&lt;/code&gt; tracks service providers with ratings and auto-creates tasks when maintenance is due.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Health tracking&lt;/strong&gt; handles OB appointments, medications, prescription refills, and vet visits. When my wife has an appointment, the system proactively creates prep tasks: grab insurance cards, calculate leave-by time with 15 minutes buffer based on Google Maps drive time, remind me to clean the car.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Content pipeline&lt;/strong&gt; — this one surprised me. The &lt;code&gt;content-manager&lt;/code&gt; agent tracks trending topics, manages an editorial calendar, coordinates with the &lt;a href="https://late.so" rel="noopener noreferrer"&gt;Late/Zernio&lt;/a&gt; social media scheduler, and even triggers blog article creation. The blog post you're reading right now was written by a &lt;code&gt;blog-writer&lt;/code&gt; agent that researched the repo, wrote the draft, ran parallel reviews through Claude Opus and GPT Codex, and created a PR for my review.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Part Nobody Expects: It Learns
&lt;/h2&gt;

&lt;p&gt;The most powerful feature isn't any single agent — it's the continuous learning loop.&lt;/p&gt;

&lt;p&gt;When I told the system "don't suggest recipes — I decide what to cook," that correction got persisted to three places: cross-session memory via &lt;code&gt;store_memory&lt;/code&gt;, behavioral rules in &lt;code&gt;data/standing-orders.md&lt;/code&gt;, and future session instructions in &lt;code&gt;.github/copilot-instructions.md&lt;/code&gt;. The system will never suggest a recipe again.&lt;/p&gt;

&lt;p&gt;When my wife didn't respond to a long message asking five questions at once, I taught the system: &lt;em&gt;"SHORT messages only — 2-3 lines max. ONE question at a time. She's pregnant with twins — respect her energy."&lt;/em&gt; That rule now governs every agent that communicates with her.&lt;/p&gt;

&lt;p&gt;Every correction is permanent. The family is essentially training a personal AI that gets better every day. After three months, the standing orders file is dense with learned behaviors — things like "Hector's ADD brain needs tasks one at a time" and "always compute Central Time fresh from PowerShell, never trust UTC headers."&lt;/p&gt;

&lt;h2&gt;
  
  
  Safety: The OpenShell Layer
&lt;/h2&gt;

&lt;p&gt;Running autonomous agents that can read email, create calendar events, and send messages is a trust question. I've written extensively about &lt;a href="https://htek.dev/articles/nvidia-openshell-sandbox-ai-agents/" rel="noopener noreferrer"&gt;sandboxing agents with NVIDIA OpenShell&lt;/a&gt; — the project that provides kernel-level filesystem isolation, network policy enforcement, and process sandboxing for AI agents.&lt;/p&gt;

&lt;p&gt;The Copilot Home Assistant uses the same layered safety approach I covered in the &lt;a href="https://htek.dev/articles/safe-openclaw-cron-iac-openshell/" rel="noopener noreferrer"&gt;Safe OpenClaw series&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Constitution-level rules&lt;/strong&gt; — agents know what they can and can't do autonomously&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extension-level hooks&lt;/strong&gt; — &lt;a href="https://htek.dev/articles/agent-hooks-controlling-ai-codebase/" rel="noopener noreferrer"&gt;agent hooks&lt;/a&gt; intercept tool calls and enforce policies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenShell sandboxing&lt;/strong&gt; — kernel-level enforcement for the paranoid (recommended)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask-via-Telegram&lt;/strong&gt; — high-stakes decisions route a confirmation prompt to your phone&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;a href="https://github.com/htekdev/gh-cli-telegram-extension" rel="noopener noreferrer"&gt;Telegram bridge extension&lt;/a&gt; itself is open source too. It started as a proof of concept and became the &lt;a href="https://htek.dev/articles/copilot-cli-telegram-bridge-mobile-ai-terminal/" rel="noopener noreferrer"&gt;communication backbone&lt;/a&gt; of the entire system.&lt;/p&gt;

&lt;p&gt;Here's how the bridge routes incoming Telegram messages to the right agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// From .github/extensions/telegram-bridge/extension.mjs&lt;/span&gt;
&lt;span class="nx"&gt;onUserPromptSubmitted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[Telegram from&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Extract sender identity from the Telegram message&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userMatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\[&lt;/span&gt;&lt;span class="sr"&gt;Telegram from &lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;.+&lt;/span&gt;&lt;span class="se"&gt;?)&lt;/span&gt;&lt;span class="sr"&gt; &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="sr"&gt;user &lt;/span&gt;&lt;span class="se"&gt;(\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)\)\]&lt;/span&gt;&lt;span class="sr"&gt;: &lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;.+&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;userMatch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[,&lt;/span&gt; &lt;span class="nx"&gt;senderName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;senderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userMessage&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;userMatch&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Inject context: "Pick the best agent and delegate"&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;additionalContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="s2"&gt;`Message from &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;senderName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. `&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
      &lt;span class="s2"&gt;`Pick the best agent_type, launch it with the task tool, `&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
      &lt;span class="s2"&gt;`and the agent responds via telegram_send_message.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every Telegram message gets tagged with the sender's identity, then Copilot CLI picks the right domain agent to handle it. I text "what's for dinner?" and the &lt;code&gt;nutrition-chef&lt;/code&gt; agent responds. I text "what tasks do I have?" and the &lt;code&gt;task-coach&lt;/code&gt; takes over.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Use It
&lt;/h2&gt;

&lt;p&gt;The repo is designed to be forked and customized:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Clone it&lt;/strong&gt;: &lt;code&gt;git clone https://github.com/htekdev/copilot-home-assistant&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configure Telegram&lt;/strong&gt;: Create a bot via &lt;a href="https://core.telegram.org/bots#how-do-i-create-a-bot" rel="noopener noreferrer"&gt;@BotFather&lt;/a&gt;, add the token to &lt;code&gt;.env&lt;/code&gt;, and set &lt;code&gt;TELEGRAM_ALLOWED_USERS&lt;/code&gt; with your chat ID&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Personalize&lt;/strong&gt;: Edit family profiles in &lt;code&gt;data/family/&lt;/code&gt;, update the constitution, set your locations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable integrations&lt;/strong&gt;: Add Google OAuth credentials for Calendar/Gmail/Tasks, Maps API key for drive times&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Start it&lt;/strong&gt;: Run &lt;code&gt;copilot-cli&lt;/code&gt; — the Telegram bridge and cron scheduler start automatically via extensions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You don't need all 17 agents. Trim to what fits your family — maybe you don't have pets (remove &lt;code&gt;dog-parent&lt;/code&gt;), don't create content (remove &lt;code&gt;content-manager&lt;/code&gt;), or your kids are too young for curriculum tracking (remove &lt;code&gt;teacher&lt;/code&gt;). The system is modular by design.&lt;/p&gt;

&lt;p&gt;Want to add an agent? Copy a template from &lt;code&gt;.github/agents/templates/&lt;/code&gt;, define its domain, decision rules, and memory file, then add a cron entry if it should run on a schedule. Agents are Markdown — not code — so anyone can create them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Copilot CLI?
&lt;/h2&gt;

&lt;p&gt;People ask why I didn't use LangChain, CrewAI, AutoGen, or any of the agent frameworks. The answer: &lt;a href="https://htek.dev/articles/your-god-prompt-is-the-new-monolith/" rel="noopener noreferrer"&gt;the god prompt is the new monolith&lt;/a&gt;. Those frameworks add orchestration layers, custom runtimes, and dependency chains. Copilot CLI gives me an AI that can run shell commands, edit files, call APIs, and use the &lt;a href="https://htek.dev/articles/copilot-cli-extensions-cookbook-examples/" rel="noopener noreferrer"&gt;extension SDK&lt;/a&gt; to expose any tool I need — with zero framework overhead.&lt;/p&gt;

&lt;p&gt;The agents are literally Markdown files. The extensions are single &lt;code&gt;.mjs&lt;/code&gt; files. The cron scheduler is a JSON config. There's no build step, no compilation, no Docker, no Kubernetes. I edit a &lt;code&gt;.md&lt;/code&gt; file and the agent's behavior changes instantly.&lt;/p&gt;

&lt;p&gt;That simplicity is the whole point. This system runs my family's life — I need to be able to debug it at 2 AM when the morning briefing breaks, not trace through six layers of orchestration abstractions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;I built this because I needed it. Managing a household with a full-time engineering job, a 4-year-old, twin babies on the way, two dogs, a content pipeline, and an ADD brain that loses context every time someone says "hey, can you also..." — that's not a personal productivity problem. That's an orchestration problem.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/htekdev/copilot-home-assistant" rel="noopener noreferrer"&gt;Copilot Home Assistant&lt;/a&gt; treats it like one. Seventeen agents, each owning a domain, communicating through a shared constitution, learning from corrections, and running autonomously on cron schedules. The system handles the remembering so I can focus on the doing.&lt;/p&gt;

&lt;p&gt;Fork it. Customize it. Make it yours. And if you build something cool with it, &lt;a href="https://github.com/htekdev/copilot-home-assistant/issues" rel="noopener noreferrer"&gt;open an issue&lt;/a&gt; — I'd love to see what your family's version looks like.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>github</category>
      <category>automation</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Your AI Terminal, In Your Pocket: Connect GitHub Copilot CLI to Telegram</title>
      <dc:creator>Hector Flores</dc:creator>
      <pubDate>Sat, 11 Apr 2026 22:06:38 +0000</pubDate>
      <link>https://dev.to/htekdev/your-ai-terminal-in-your-pocket-connect-github-copilot-cli-to-telegram-4641</link>
      <guid>https://dev.to/htekdev/your-ai-terminal-in-your-pocket-connect-github-copilot-cli-to-telegram-4641</guid>
      <description>&lt;h2&gt;
  
  
  Why Would You Want This?
&lt;/h2&gt;

&lt;p&gt;Picture this: you're walking the dog, waiting at the dentist, or three beers deep at a barbecue — and you need to check on a deployment, ask your agent to look something up, or kick off a data pipeline. You pull out your phone, open Telegram, type a message, and your GitHub Copilot CLI session on your desktop responds. The answer comes back right there in the chat.&lt;/p&gt;

&lt;p&gt;That's what we're building. A &lt;strong&gt;Telegram bridge extension&lt;/strong&gt; that turns your Copilot CLI session into a bidirectional chat — every message you send in Telegram becomes a prompt in the CLI, and every assistant response gets forwarded back. No VPN, no SSH, no remote desktop. Just Telegram.&lt;/p&gt;

&lt;p&gt;By the end of this guide, you'll have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📱 &lt;strong&gt;Bidirectional messaging&lt;/strong&gt; — Telegram ↔ Copilot CLI&lt;/li&gt;
&lt;li&gt;📸 &lt;strong&gt;Photo support&lt;/strong&gt; — send images from Telegram for vision analysis&lt;/li&gt;
&lt;li&gt;🎤 &lt;strong&gt;Voice notes&lt;/strong&gt; — transcribed via OpenAI Whisper and forwarded as text prompts&lt;/li&gt;
&lt;li&gt;⏰ &lt;strong&gt;Cron jobs&lt;/strong&gt; — scheduled tasks that run automatically (daily briefings, heartbeat checks)&lt;/li&gt;
&lt;li&gt;🛠️ &lt;strong&gt;Custom tools&lt;/strong&gt; — &lt;code&gt;telegram_send_message&lt;/code&gt;, &lt;code&gt;telegram_send_photo&lt;/code&gt;, &lt;code&gt;telegram_get_status&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of this runs as a &lt;strong&gt;Copilot CLI extension&lt;/strong&gt; — no external servers, no Docker, no cloud functions. Just a single &lt;code&gt;.mjs&lt;/code&gt; file in your repo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before we start, make sure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Copilot CLI&lt;/strong&gt; installed and working (&lt;code&gt;gh copilot&lt;/code&gt; or the standalone &lt;code&gt;copilot&lt;/code&gt; binary)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node.js 18+&lt;/strong&gt; (the extension uses &lt;code&gt;fetch&lt;/code&gt;, &lt;code&gt;AbortController&lt;/code&gt;, and ES modules)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Telegram account&lt;/strong&gt; (obviously)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;~15 minutes&lt;/strong&gt; of your time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Optional for voice note transcription:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;An OpenAI API key&lt;/strong&gt; with access to the Whisper model&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Create a Telegram Bot
&lt;/h2&gt;

&lt;p&gt;Every Telegram bot starts with &lt;a href="https://t.me/BotFather" rel="noopener noreferrer"&gt;@BotFather&lt;/a&gt;. Open it in Telegram and run through the flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Send &lt;code&gt;/newbot&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Pick a display name (e.g., "Copilot CLI Bridge")&lt;/li&gt;
&lt;li&gt;Pick a username — must end in &lt;code&gt;bot&lt;/code&gt; (e.g., &lt;code&gt;my_copilot_cli_bot&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;BotFather replies with your &lt;strong&gt;bot token&lt;/strong&gt; — something like &lt;code&gt;7123456789:AAH...&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Save that token.&lt;/strong&gt; You'll need it in Step 3.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Tip:&lt;/strong&gt; Also send &lt;code&gt;/setdescription&lt;/code&gt; to give your bot a description. And &lt;code&gt;/setuserpic&lt;/code&gt; if you want it to look professional. I use the GitHub Copilot logo.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now open a chat with your new bot and send &lt;code&gt;/start&lt;/code&gt;. This establishes the chat and gets it ready to receive messages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Build the Extension
&lt;/h2&gt;

&lt;p&gt;Create the extension directory and file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.github/
  extensions/
    telegram-bridge/
      extension.mjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a single file. Let me walk through each section.&lt;/p&gt;

&lt;h3&gt;
  
  
  2a. Imports and Configuration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="c1"&gt;// Read config from .env file&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ENV_FILE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.env&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;TELEGRAM_TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TELEGRAM_BOT_TOKEN&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;TELEGRAM_CHAT_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TELEGRAM_CHAT_ID&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;parseEnvFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;existsSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf-8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;trimmed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;trimmed&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;trimmed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;eqIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;trimmed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eqIndex&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;trimmed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;eqIndex&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;trimmed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eqIndex&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// Strip quotes&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TELEGRAM_BOT_TOKEN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;TELEGRAM_TOKEN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;TELEGRAM_TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TELEGRAM_CHAT_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;TELEGRAM_CHAT_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;TELEGRAM_CHAT_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;parseEnvFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ENV_FILE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The extension reads &lt;code&gt;TELEGRAM_BOT_TOKEN&lt;/code&gt; from either environment variables or a &lt;code&gt;.env&lt;/code&gt; file in your project root. &lt;code&gt;TELEGRAM_CHAT_ID&lt;/code&gt; is optional — if set, it locks the bot to a single chat for security.&lt;/p&gt;

&lt;h3&gt;
  
  
  2b. Telegram API Helpers
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;API_BASE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://api.telegram.org/bot&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;TELEGRAM_TOKEN&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;telegramApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;API_BASE&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Telegram API error: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the core of the Telegram integration. The &lt;a href="https://core.telegram.org/bots/api" rel="noopener noreferrer"&gt;Bot API&lt;/a&gt; is HTTP-based and dead simple — &lt;code&gt;POST&lt;/code&gt; to &lt;code&gt;https://api.telegram.org/bot&amp;lt;token&amp;gt;/&amp;lt;method&amp;gt;&lt;/code&gt; with a JSON body. No SDK needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  2c. Message Sending With Chunking
&lt;/h3&gt;

&lt;p&gt;Telegram has a &lt;strong&gt;4096-character limit&lt;/strong&gt; per message. Long assistant responses will blow right past that. Here's the sender that handles chunking and HTML formatting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TELEGRAM_MAX_LENGTH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sendTelegramMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chatId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Detect if the text already contains HTML tags&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isHtml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/&amp;lt;&lt;/span&gt;&lt;span class="se"&gt;(?:&lt;/span&gt;&lt;span class="sr"&gt;b|i|u|s|a|code|pre|em|strong&lt;/span&gt;&lt;span class="se"&gt;)\b[^&lt;/span&gt;&lt;span class="sr"&gt;&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;*&amp;gt;/i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;TELEGRAM_MAX_LENGTH&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;TELEGRAM_MAX_LENGTH&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isHtml&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;telegramApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sendMessage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;chat_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;chatId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;sanitizeTelegramHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="na"&gt;parse_mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HTML&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;telegramApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sendMessage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;chat_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;chatId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;markdownToTelegramHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="na"&gt;parse_mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HTML&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Fallback to plain text if formatting fails&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;plain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&amp;lt;&lt;/span&gt;&lt;span class="se"&gt;[^&lt;/span&gt;&lt;span class="sr"&gt;&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&amp;gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&amp;amp;amp;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&amp;amp;lt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&amp;amp;gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;telegramApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sendMessage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;chat_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;chatId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;plain&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// Rate limit between chunks&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fallback is key. Telegram's HTML parser is strict — if a tag isn't closed properly, the entire message fails. By catching errors and retrying with plain text, we never lose a message.&lt;/p&gt;

&lt;p&gt;Here are the formatting helpers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Telegram only supports: b, strong, i, em, u, ins, s, strike, del, a, code, pre&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sanitizeTelegramHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="sr"&gt;/&amp;lt;&lt;/span&gt;&lt;span class="se"&gt;\/?(?!&lt;/span&gt;&lt;span class="sr"&gt;b|strong|i|em|u|ins|s|strike|del|a|code|pre|&lt;/span&gt;&lt;span class="se"&gt;\/)[&lt;/span&gt;&lt;span class="sr"&gt;a-z&lt;/span&gt;&lt;span class="se"&gt;][^&lt;/span&gt;&lt;span class="sr"&gt;&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;*&amp;gt;/gi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;""&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&amp;amp;amp;amp;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;amp;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Convert markdown-ish text to Telegram-safe HTML&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;markdownToTelegramHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&amp;amp;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;amp;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&amp;lt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;lt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&amp;gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\*\*(&lt;/span&gt;&lt;span class="sr"&gt;.+&lt;/span&gt;&lt;span class="se"&gt;?)\*\*&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;b&amp;gt;$1&amp;lt;/b&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/__&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;.+&lt;/span&gt;&lt;span class="se"&gt;?)&lt;/span&gt;&lt;span class="sr"&gt;__/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;b&amp;gt;$1&amp;lt;/b&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;(?&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;!&lt;/span&gt;&lt;span class="se"&gt;\w)\*([^&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;?)\*(?!\w)&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;i&amp;gt;$1&amp;lt;/i&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;(?&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;!&lt;/span&gt;&lt;span class="se"&gt;\w)&lt;/span&gt;&lt;span class="sr"&gt;_&lt;/span&gt;&lt;span class="se"&gt;([^&lt;/span&gt;&lt;span class="sr"&gt;_&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;?)&lt;/span&gt;&lt;span class="sr"&gt;_&lt;/span&gt;&lt;span class="se"&gt;(?!\w)&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;i&amp;gt;$1&amp;lt;/i&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/`&lt;/span&gt;&lt;span class="se"&gt;([^&lt;/span&gt;&lt;span class="sr"&gt;`&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;?)&lt;/span&gt;&lt;span class="sr"&gt;`/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;code&amp;gt;$1&amp;lt;/code&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\[([^\]]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)\]\(([^&lt;/span&gt;&lt;span class="sr"&gt;)&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)\)&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;a href="$2"&amp;gt;$1&amp;lt;/a&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2d. Long Polling Loop
&lt;/h3&gt;

&lt;p&gt;This is the heart of the extension. &lt;a href="https://core.telegram.org/bots/api#getupdates" rel="noopener noreferrer"&gt;Long polling&lt;/a&gt; is Telegram's recommended way to receive messages without a webhook server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;running&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;pollOffset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;activeChatId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;TELEGRAM_CHAT_ID&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;skipOldUpdates&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;telegramApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;getUpdates&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;pollOffset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;update_id&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cm"&gt;/* start from 0 */&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;pollLoop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;running&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Let any previous instance release&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;skipOldUpdates&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;me&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;telegramApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;getMe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`🤖 Telegram bot connected: @&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;me&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;me&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;first_name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`⚠️ Could not verify bot: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;warning&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;📡 Telegram long polling started&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;running&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;API_BASE&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/getUpdates`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pollOffset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;allowed_updates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;pollOffset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;update_id&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chatId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;first_name&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unknown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// Security: restrict to configured chat&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TELEGRAM_CHAT_ID&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;chatId&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;TELEGRAM_CHAT_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendTelegramMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nx"&gt;chatId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;⛔ Unauthorized. This bot is locked to a specific chat.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
          &lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nx"&gt;activeChatId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;chatId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// Handle /start, /status, /help commands&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/start&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendTelegramMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nx"&gt;chatId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;`✅ Connected! Your chat ID is: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;chatId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n\n`&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
              &lt;span class="s2"&gt;`Send any message to forward it to your Copilot CLI session.`&lt;/span&gt;
          &lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendTelegramMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nx"&gt;chatId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;`📡 Bridge active\n• Chat: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;chatId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n• Polling: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;running&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
          &lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Forward text to session&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`💬 [Telegram] &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="nx"&gt;session&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
              &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`[Telegram from &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;]: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;immediate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt;
          &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AbortError&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`⚠️ Poll error: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;warning&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few design decisions worth calling out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;skipOldUpdates()&lt;/code&gt;&lt;/strong&gt; — On startup, we skip any messages that arrived while the bot was offline. Without this, you'd get a flood of old messages replayed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;mode: "immediate"&lt;/code&gt;&lt;/strong&gt; — This tells the session to handle the prompt right now, even if it's in the middle of something. The alternative is &lt;code&gt;"enqueue"&lt;/code&gt;, which queues it for later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;timeout: 10&lt;/code&gt;&lt;/strong&gt; in &lt;code&gt;getUpdates&lt;/code&gt; — Telegram holds the connection open for 10 seconds, then returns empty if no messages arrived. This is long polling: low latency, low CPU, no webhook infrastructure needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 3: Configure Your Environment
&lt;/h2&gt;

&lt;p&gt;Create a &lt;code&gt;.env&lt;/code&gt; file in your project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;TELEGRAM_BOT_TOKEN&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;7123456789:AAH_your_token_here&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the minimum. Optionally, lock the bot to your chat:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;TELEGRAM_BOT_TOKEN&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;7123456789:AAH_your_token_here&lt;/span&gt;
&lt;span class="py"&gt;TELEGRAM_CHAT_ID&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;123456789&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;How to get your chat ID:&lt;/strong&gt; Send &lt;code&gt;/start&lt;/code&gt; to your bot, then check the Telegram API response — it contains your &lt;code&gt;chat.id&lt;/code&gt;. Or just start the extension and look at the logs when you send a message.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Add &lt;code&gt;.env&lt;/code&gt; to your &lt;code&gt;.gitignore&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;".env"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; .gitignore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Register Tools
&lt;/h2&gt;

&lt;p&gt;The extension exposes three tools to the Copilot CLI session. These go in the &lt;code&gt;joinSession()&lt;/code&gt; call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;joinSession&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;hooks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;onSessionStart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;TELEGRAM_TOKEN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;additionalContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[telegram-bridge] Not active — set TELEGRAM_BOT_TOKEN in .env.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;additionalContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[telegram-bridge] Telegram bridge is ACTIVE. &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Incoming messages appear as prompts. &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Responses are auto-forwarded to Telegram.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;telegram_send_message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Send an explicit message to the connected Telegram chat.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The message text to send to Telegram&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;chat_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Optional: specific chat ID. Defaults to active chat.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;targetChat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat_id&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;activeChatId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;targetChat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;No active Telegram chat. Message the bot first.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendTelegramMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;targetChat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`Message sent to chat &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;targetChat&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;telegram_send_photo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Send a photo to the connected Telegram chat. &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Accepts a local file path or URL.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Path to local image or URL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;caption&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Optional caption (max 1024 chars)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;chat_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Optional: specific chat ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;photo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;targetChat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat_id&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;activeChatId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;targetChat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;No active Telegram chat.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
          &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;chat_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;targetChat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;photo&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;caption&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;caption&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;caption&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;telegramApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sendPhoto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
          &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chat_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;targetChat&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;photo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;fileData&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="nf"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;caption&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;caption&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;caption&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;API_BASE&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/sendPhoto`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;});&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`Photo sent to chat &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;targetChat&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;telegram_get_status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Check the Telegram bridge connection status.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;configured&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;TELEGRAM_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;polling&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;running&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;activeChatId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;activeChatId&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These tools let the agent proactively send content to Telegram — status updates, generated reports, photos, anything. Without them, the only messages going to Telegram would be auto-forwarded assistant responses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Auto-Forward Responses
&lt;/h2&gt;

&lt;p&gt;This is the magic that makes it feel like a real chat. After &lt;code&gt;joinSession()&lt;/code&gt;, subscribe to the session's event stream:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;assistant.message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;activeChatId&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;TELEGRAM_TOKEN&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;running&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;TELEGRAM_MAX_LENGTH&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;TELEGRAM_MAX_LENGTH&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;telegramApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sendMessage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;chat_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;activeChatId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;parse_mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Markdown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Fallback to plain text&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;telegramApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sendMessage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;chat_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;activeChatId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`⚠️ Forward failed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;warning&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every time the assistant finishes a response, it gets forwarded to Telegram. We use &lt;code&gt;parse_mode: "Markdown"&lt;/code&gt; for auto-forwarded messages because assistant responses usually contain markdown. If parsing fails (unclosed backticks, weird formatting), the fallback sends plain text.&lt;/p&gt;

&lt;h3&gt;
  
  
  Start Polling on Load
&lt;/h3&gt;

&lt;p&gt;Finally, kick off the polling loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TELEGRAM_TOKEN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;pollLoop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`❌ Polling crashed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;⚠️ TELEGRAM_BOT_TOKEN not found — bridge disabled&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;warning&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 6: Test It
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start a Copilot CLI session&lt;/strong&gt; in your project directory:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   copilot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Look for the startup logs:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   🤖 Telegram bot connected: @my_copilot_cli_bot (Copilot CLI Bridge)
   📡 Telegram long polling started — waiting for messages
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Open Telegram&lt;/strong&gt; and send your bot a message:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   What time is it?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;You should see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the CLI: &lt;code&gt;💬 [Telegram] Hector: What time is it?&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;In Telegram: the assistant's response appears&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Test the tools&lt;/strong&gt; — ask the CLI directly:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   Send a message to Telegram saying "Hello from the CLI!"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;The agent will call &lt;code&gt;telegram_send_message&lt;/code&gt; and you'll see it in Telegram.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. &lt;strong&gt;You now have a bidirectional Telegram ↔ Copilot CLI bridge.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: Voice Notes With Whisper
&lt;/h2&gt;

&lt;p&gt;Want to send voice notes from Telegram and have them transcribed into text prompts? Add this to the polling loop's message handler, right after the text message handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Handle voice notes&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;voice&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;voiceObj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;voice&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`🎤 [Telegram] &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: voice (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;voiceObj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;s)`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;openaiKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;openaiKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendTelegramMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chatId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;⚠️ No OpenAI key for transcription.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="c1"&gt;// Download voice file from Telegram&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;telegramApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;getFile&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;file_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;voiceObj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="s2"&gt;`https://api.telegram.org/file/bot&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;TELEGRAM_TOKEN&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fileInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;audioRes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;audioBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;audioRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

      &lt;span class="c1"&gt;// Build multipart form for Whisper API&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;boundary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;----WhisperBoundary&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file_path&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;voice.ogg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`voice.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ext&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mimeMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;ogg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;audio/ogg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;oga&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;audio/ogg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;mp3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;audio/mpeg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;m4a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;audio/mp4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;wav&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;audio/wav&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mimeMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ext&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;audio/ogg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;`--&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;boundary&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\r\nContent-Disposition: form-data; name="file"; filename="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"\r\nContent-Type: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;mime&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\r\n\r\n`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;audioBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;`\r\n--&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;boundary&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\r\nContent-Disposition: form-data; name="model"\r\n\r\nwhisper-1\r\n--&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;boundary&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;--\r\n`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bodyBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
        &lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
      &lt;span class="p"&gt;]);&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;whisperRes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.openai.com/v1/audio/transcriptions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;openaiKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`multipart/form-data; boundary=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;boundary&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bodyBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;whisperRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Whisper &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;whisperRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;whisperRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transcript&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;(empty)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`🎤 Transcribed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;...`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`[Telegram from &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;]: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;immediate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendTelegramMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;chatId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;`⚠️ Transcription failed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add &lt;code&gt;OPENAI_API_KEY&lt;/code&gt; to your &lt;code&gt;.env&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;sk-...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can hold down the mic button in Telegram, speak your prompt, and it gets transcribed and fed to the CLI session as text. I use this constantly when I'm on the move.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: Add Cron Jobs
&lt;/h2&gt;

&lt;p&gt;Once you have Telegram working, the natural next question is: "Can the agent do things on a schedule?" Yes. Let's build a cron scheduler extension.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Cron Scheduler Extension
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;.github/extensions/cron-scheduler/extension.mjs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="c1"&gt;// ---- Cron parser (5-field: min hour dom month dow) ----&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;parseCronField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;part&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stepMatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;.+&lt;/span&gt;&lt;span class="se"&gt;)\/(\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stepMatch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stepMatch&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stepMatch&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stepMatch&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rangeMatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;(\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;-&lt;/span&gt;&lt;span class="se"&gt;(\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rangeMatch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rangeMatch&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rangeMatch&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;parseCron&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Invalid cron: "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;parseCronField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;59&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;parseCronField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;daysOfMonth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;parseCronField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;months&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;parseCronField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;daysOfWeek&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;parseCronField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;cronMatches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMinutes&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getHours&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;daysOfMonth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDate&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;months&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMonth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;daysOfWeek&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDay&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;nowInTimezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tz&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en-US&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;timeZone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tz&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ---- Config ----&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CRON_FILE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cron.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CRON_ENABLED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CRON_ENABLED&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CRON_ENABLED&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UTC&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;parsedJobs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loadConfig&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;CRON_ENABLED&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;existsSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CRON_FILE&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CRON_FILE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf-8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timezone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timezone&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UTC&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;parsedJobs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;jobs&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enabled&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;parseCron&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;parsedJobs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;loadConfig&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;existsSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CRON_FILE&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;watchFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CRON_FILE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;loadConfig&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ---- Scheduler ----&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lastFired&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;minuteKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFullYear&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMonth&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDate&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getHours&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMinutes&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;checkSchedule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parsedJobs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;nowInTimezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;minuteKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;parsedJobs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;cronMatches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;mk&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lastFired&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;lastFired&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`⏰ [cron] Running: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`[Scheduled Task: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;enqueue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ---- Session ----&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;joinSession&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;hooks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;onSessionStart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;additionalContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CRON_ENABLED&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`[cron] &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;parsedJobs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; job(s) active (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[cron] Disabled — set CRON_ENABLED=true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cron_list_jobs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;List all configured cron jobs.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;loadConfig&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;No cron jobs configured.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;jobs&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`• &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; [&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enabled&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;off&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;on&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;]`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Start scheduler loop&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CRON_ENABLED&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;parsedJobs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;checkSchedule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;checkSchedule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`⏰ Cron active: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;parsedJobs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; job(s)`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The cron.json File
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;cron.json&lt;/code&gt; in your project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timezone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"America/Chicago"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"jobs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"daily-briefing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"schedule"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0 6 * * 1-5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"prompt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Give me a morning briefing: check my calendar for today, any urgent emails, and top priorities."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"heartbeat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"schedule"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*/20 6-22 * * 1-5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"prompt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Quick scan: any new emails or Teams messages in the last 20 minutes that need attention?"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cron syntax is standard 5-field: &lt;code&gt;minute hour day-of-month month day-of-week&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Schedule&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0 6 * * 1-5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;6:00 AM, Monday–Friday&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;*/20 6-22 * * 1-5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Every 20 min, 6 AM–10 PM, weekdays&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0 9,17 * * *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;9 AM and 5 PM, every day&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;30 8 1 * *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;8:30 AM on the 1st of each month&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Enable the scheduler in your &lt;code&gt;.env&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;CRON_ENABLED&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Agent-Based Cron Jobs
&lt;/h3&gt;

&lt;p&gt;For complex recurring tasks, you can point a cron job at an &lt;strong&gt;agent file&lt;/strong&gt; instead of an inline prompt. Agent files live in &lt;code&gt;.github/agents/&lt;/code&gt; and contain detailed instructions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timezone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"America/Chicago"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"jobs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"heartbeat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"schedule"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*/20 6-22 * * 1-5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"agent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"heartbeat"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the &lt;code&gt;agent&lt;/code&gt; field is present instead of &lt;code&gt;prompt&lt;/code&gt;, the scheduler reads &lt;code&gt;.github/agents/heartbeat.agent.md&lt;/code&gt; and launches a background agent with those instructions. Here's a simplified example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;heartbeat&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Scan&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;recent&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;activity&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;take&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;action"&lt;/span&gt;
&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;telegram_send_message&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;list_actions&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;add_action&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gh"&gt;# Heartbeat Agent&lt;/span&gt;

You are an autonomous assistant running a scheduled check.

&lt;span class="gu"&gt;## Phase 1: Scan Inbound Activity&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Check emails received in the last 20 minutes
&lt;span class="p"&gt;-&lt;/span&gt; Check Teams messages received in the last 20 minutes
&lt;span class="p"&gt;-&lt;/span&gt; If nothing new: stay silent, return "No activity."

&lt;span class="gu"&gt;## Phase 2: Act&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; For each message, classify: needs response? needs action item?
&lt;span class="p"&gt;-&lt;/span&gt; Auto-reply to simple acknowledgments
&lt;span class="p"&gt;-&lt;/span&gt; Escalate complex decisions to Telegram

&lt;span class="gu"&gt;## Phase 3: Housekeeping&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Create action items for follow-ups
&lt;span class="p"&gt;-&lt;/span&gt; Update existing items if a message resolves one
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is powerful. Your Copilot CLI session becomes an autonomous assistant that checks your communications, takes routine actions, and escalates to your phone via Telegram when it needs your judgment.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;Once you have the bridge and cron running, the possibilities open up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Watch lists&lt;/strong&gt; — Track specific threads or customers and get notified when they reply&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-reply rules&lt;/strong&gt; — Let the agent handle routine messages with predefined response templates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom agents&lt;/strong&gt; — Build specialized agents (daily report, code reviewer, data pipeline) and schedule them via cron&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Photo analysis&lt;/strong&gt; — Send screenshots from your phone and ask the agent to analyze them (the extension already supports photo forwarding via vision)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-device&lt;/strong&gt; — Add the same bot to a Telegram group so your team can interact with the agent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full production version of this extension (with typing indicators, conflict handling, photo uploads, and the standalone bridge mode toggle) is about 750 lines. What I've shown here is the core — everything you need to get started. Once it's working, you'll find yourself reaching for your phone to talk to your CLI more than you'd expect.&lt;/p&gt;

&lt;p&gt;The code runs locally on your machine. Your messages go through Telegram's servers (encrypted in transit), but the agent, the tools, and all your data stay on your box. No cloud functions, no middleware, no third-party agent platforms. Just Telegram's Bot API, the Copilot CLI extension SDK, and a &lt;code&gt;.env&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;That's the whole thing. Go build it.&lt;/p&gt;

</description>
      <category>github</category>
      <category>telegram</category>
      <category>extensions</category>
      <category>automation</category>
    </item>
    <item>
      <title>VS Code Weekly: Agentic Dev Gets Real with 1.115</title>
      <dc:creator>Hector Flores</dc:creator>
      <pubDate>Wed, 08 Apr 2026 14:14:20 +0000</pubDate>
      <link>https://dev.to/htekdev/vs-code-weekly-agentic-dev-gets-real-with-1115-4216</link>
      <guid>https://dev.to/htekdev/vs-code-weekly-agentic-dev-gets-real-with-1115-4216</guid>
      <description>&lt;h2&gt;
  
  
  Microsoft Just Shipped Twice in One Week
&lt;/h2&gt;

&lt;p&gt;VS Code 1.115 dropped this morning, hot on the heels of 1.114 last Wednesday. This rapid back-to-back shipping pace is wild, but what's more interesting is &lt;em&gt;what&lt;/em&gt; they're shipping: the team is racing to make agentic workflows feel native, not bolted on.&lt;/p&gt;

&lt;p&gt;Let me break down what actually matters from these two releases, because there's a clear story here about where Microsoft thinks AI-assisted development is going.&lt;/p&gt;

&lt;h2&gt;
  
  
  The #codebase Overhaul: Semantic Search Gets Serious
&lt;/h2&gt;

&lt;p&gt;The biggest change in 1.114 is one that most people will barely notice — but it's critical. Microsoft ripped out all the complexity around workspace indexing and made &lt;code&gt;#codebase&lt;/code&gt; purely semantic.&lt;/p&gt;

&lt;p&gt;Previously, the &lt;code&gt;#codebase&lt;/code&gt; tool in Copilot Chat would fall back to fuzzy text searches when semantic indexes weren't available. There were "local indexes" (a few thousand files, not always semantic) and "remote indexes" (millions of files, shared across teams). You had to manage this mess yourself.&lt;/p&gt;

&lt;p&gt;Now? There's one state: &lt;strong&gt;indexed or not indexed&lt;/strong&gt;. No more local vs. remote. No more half-semantic fallbacks. If your codebase is indexed, &lt;code&gt;#codebase&lt;/code&gt; runs semantic searches. If it's not, agents use other methods (text, grep, symbols).&lt;/p&gt;

&lt;p&gt;Why this matters: Agentic workflows fail when tools return inconsistent results. If &lt;code&gt;#codebase&lt;/code&gt; sometimes returns semantic matches and sometimes returns dumb fuzzy results, agents can't build reliable prompts around it. By making the behavior predictable, Microsoft is giving agents a tool they can actually use correctly.&lt;/p&gt;

&lt;p&gt;The tradeoff: Large codebases without GitHub repos might not be indexable yet. Microsoft is rolling out support slowly. But in my testing, agents still get decent results using fallback methods for non-indexed workspaces.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bottom line&lt;/strong&gt;: This is what "agentic-first design" looks like. Not flashy, but crucial for making agents trustworthy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chat UX Polish: The Little Things Add Up
&lt;/h2&gt;

&lt;p&gt;1.114 also shipped a handful of chat refinements that sound minor but are surprisingly high-impact:&lt;/p&gt;

&lt;h3&gt;
  
  
  Copy Final Response
&lt;/h3&gt;

&lt;p&gt;The new &lt;strong&gt;Copy Final Response&lt;/strong&gt; command strips out all the agent's internal thinking, tool calls, and noise — you just get the final Markdown output. This is &lt;em&gt;huge&lt;/em&gt; for sharing Copilot's work with teammates who don't need to see the sausage being made.&lt;/p&gt;

&lt;p&gt;Before this, copying a chat response meant pasting a wall of &lt;code&gt;[Running tool: workspace_search]&lt;/code&gt; and &lt;code&gt;[Thinking...]&lt;/code&gt; garbage. Now you get clean Markdown you can drop into a PR comment or Slack thread.&lt;/p&gt;

&lt;h3&gt;
  
  
  Video in the Image Carousel
&lt;/h3&gt;

&lt;p&gt;The image carousel (introduced in 1.113) now supports videos. You can preview videos from chat attachments or the Explorer context menu with playback controls and thumbnail navigation.&lt;/p&gt;

&lt;p&gt;This isn't revolutionary, but it's another sign that VS Code is becoming a full IDE for multimodal content — not just code and images, but video, audio, and whatever comes next.&lt;/p&gt;

&lt;h3&gt;
  
  
  Troubleshoot Previous Sessions
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;/troubleshoot&lt;/code&gt; skill can now analyze &lt;em&gt;previous&lt;/em&gt; chat sessions using &lt;code&gt;#session&lt;/code&gt; references. This is critical for debugging why custom instructions were ignored or why responses were slow — you can investigate after the fact without reproducing the issue.&lt;/p&gt;

&lt;p&gt;From an enterprise perspective, this is a huge win for debugging Copilot customization issues at scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  VS Code 1.115: Agent Host Protocol Goes Hard
&lt;/h2&gt;

&lt;p&gt;1.115, which landed this morning, doubles down on the "agentic dev" bet with a cluster of Agent Host Protocol improvements. If you're not familiar, the Agent Host Protocol is VS Code's framework for running AI agents inside the editor with structured access to tools, terminals, and context.&lt;/p&gt;

&lt;p&gt;Here's what shipped:&lt;/p&gt;

&lt;h3&gt;
  
  
  Background Terminal Upgrades
&lt;/h3&gt;

&lt;p&gt;Background terminals now &lt;strong&gt;automatically notify agents when commands complete&lt;/strong&gt;, including the exit code and output. Prompts for input are also detected and surfaced.&lt;/p&gt;

&lt;p&gt;This fixes a longstanding issue: when agents ran commands in the background using &lt;code&gt;run_in_terminal&lt;/code&gt; with a timeout, terminals would become read-only and lose state. The new &lt;code&gt;send_to_terminal&lt;/code&gt; tool sends commands with user confirmation and preserves terminal interactivity.&lt;/p&gt;

&lt;p&gt;I've hit this exact problem dozens of times. Agents would fire off &lt;code&gt;npm install&lt;/code&gt; in the background and then have no idea if it succeeded or failed. Now they get clean signal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Agent Session File Edit Tracking
&lt;/h3&gt;

&lt;p&gt;VS Code now tracks and restores &lt;strong&gt;file edits in agent sessions&lt;/strong&gt;, delivering diffs, undo/redo, and state restoration for customizations made during a session.&lt;/p&gt;

&lt;p&gt;This means agents can make exploratory changes to your codebase, revert them cleanly, and track exactly what they modified. It's the foundation for workflows where agents iterate on solutions without leaving a mess behind.&lt;/p&gt;

&lt;h3&gt;
  
  
  Browser Tab Tracking for Agents
&lt;/h3&gt;

&lt;p&gt;Chat can now track and link to &lt;strong&gt;browser tabs&lt;/strong&gt; that were opened or interacted with during a session. Agents can reference open web pages in their context.&lt;/p&gt;

&lt;p&gt;Imagine an agent debugging a web app: it opens the page in VS Code's integrated browser, inspects the rendered output, and references specific elements in its next prompt. That's the workflow this enables.&lt;/p&gt;

&lt;h3&gt;
  
  
  SSH Remote Connection Automation
&lt;/h3&gt;

&lt;p&gt;VS Code can now connect to remote machines over SSH and &lt;strong&gt;automatically install the CLI and start it in agent host mode&lt;/strong&gt;. This is a major quality-of-life improvement for teams running agents on remote dev boxes or cloud workstations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other Notable Bits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Test coverage in the minimap&lt;/strong&gt;: You can now see coverage indicators directly in the minimap (finally).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terminal file paste&lt;/strong&gt;: Drag-and-drop images or files into the terminal, or paste with Ctrl+V. Surprisingly useful for SSH workflows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pinch-to-zoom in integrated browser&lt;/strong&gt;: Mac users can now pinch-to-zoom in the integrated browser. Small, but polished.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  TypeScript 6.0 Ships (And 7.0 Is Coming)
&lt;/h2&gt;

&lt;p&gt;Buried in 1.114 is the TypeScript 6.0 update. This major release includes important cleanup work and &lt;strong&gt;deprecates older options&lt;/strong&gt; in preparation for the TypeScript 7.0 rewrite.&lt;/p&gt;

&lt;p&gt;If you're maintaining TypeScript tooling or extensions, pay attention: TypeScript 7.0 is a ground-up rewrite with breaking changes. The team is using 6.0 as a transition release to surface deprecation warnings early.&lt;/p&gt;

&lt;p&gt;Read the &lt;a href="https://devblogs.microsoft.com/typescript/announcing-typescript-6-0/" rel="noopener noreferrer"&gt;TypeScript 6.0 release notes&lt;/a&gt; for the full list of deprecated options.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Week Signals
&lt;/h2&gt;

&lt;p&gt;Microsoft is shipping two things in parallel:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Agent infrastructure&lt;/strong&gt; (background terminals, session tracking, file diffs, browser context)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent UX polish&lt;/strong&gt; (copy final response, troubleshoot sessions, simplified indexing)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The pattern is clear: they're not just adding features to Copilot Chat. They're building a &lt;strong&gt;platform for agentic workflows&lt;/strong&gt; where agents have structured, reliable access to tools, and the UX is designed around iterative, multi-step problem-solving.&lt;/p&gt;

&lt;p&gt;This rapid back-to-back pace helps here. Small, focused releases let them experiment with agent workflows in production without shipping half-baked features in monthly milestones.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;If you're building on VS Code's extension APIs or Copilot Chat, these two releases are a signal: &lt;strong&gt;bet on agents having more capabilities, not fewer&lt;/strong&gt;. The team is moving fast to give agents access to terminals, file systems, browsers, and remote environments with proper state management and undo.&lt;/p&gt;

&lt;p&gt;For individual developers, the takeaway is simpler: keep your VS Code updated. These rapid incremental releases are stable, and the improvements compound fast. The &lt;code&gt;#codebase&lt;/code&gt; changes alone are worth updating for if you work in large repos.&lt;/p&gt;

&lt;p&gt;We're not just getting better chat responses. We're getting a fundamentally different development environment where agents are first-class collaborators with structured access to your tools. That's the bet Microsoft is making, and these two releases are proof they're serious about it.&lt;/p&gt;

</description>
      <category>devex</category>
      <category>ai</category>
      <category>opensource</category>
      <category>vscode</category>
    </item>
    <item>
      <title>Visual Studio Weekly: Custom Copilot Agents Are Here</title>
      <dc:creator>Hector Flores</dc:creator>
      <pubDate>Wed, 08 Apr 2026 14:13:42 +0000</pubDate>
      <link>https://dev.to/htekdev/visual-studio-weekly-custom-copilot-agents-are-here-4bk2</link>
      <guid>https://dev.to/htekdev/visual-studio-weekly-custom-copilot-agents-are-here-4bk2</guid>
      <description>&lt;p&gt;Visual Studio's March 2026 update dropped last week, and it's the most significant Copilot extensibility release I've seen in the IDE. Custom agents, live profiling during debugging, and enterprise MCP governance are all here. If you've been waiting for AI tooling that actually fits your workflow instead of forcing you into a generic assistant box, this is the update.&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom Agents: Build Copilot for Your Codebase
&lt;/h2&gt;

&lt;p&gt;The headline feature is custom agents. You can now define specialized Copilot agents as &lt;code&gt;.agent.md&lt;/code&gt; files in your repository. Drop them in &lt;code&gt;.github/agents/&lt;/code&gt; and they appear in the agent picker, ready to use with full access to workspace awareness, code understanding, tools, and MCP connections.&lt;/p&gt;

&lt;p&gt;This is what AI extensibility should look like. Instead of prompting a general-purpose assistant to "follow our coding standards" every single time, you define an agent that &lt;em&gt;knows&lt;/em&gt; your standards, can run your build pipeline, query your internal docs, and enforce your team's conventions automatically.&lt;/p&gt;

&lt;p&gt;From the &lt;a href="https://devblogs.microsoft.com/visualstudio/visual-studio-march-update-build-your-own-custom-agents/" rel="noopener noreferrer"&gt;Visual Studio blog&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Want Copilot to follow your team's coding standards, run your build pipeline, or query your internal docs? Custom agents make that possible.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The implementation is straightforward. Each agent is a markdown file with instructions, tool access, and optional MCP server connections. If you don't specify a model, it uses whatever you've selected in the model picker. The &lt;a href="https://github.com/github/awesome-copilot" rel="noopener noreferrer"&gt;awesome-copilot repo&lt;/a&gt; has community-contributed examples you can use as starting points, though you'll need to verify tool names since they vary across GitHub Copilot platforms.&lt;/p&gt;

&lt;p&gt;This bridges the gap between generic AI assistants and the specialized workflows enterprises actually need. It's what I've been calling for in my &lt;a href="https://htek.dev/articles/choosing-the-right-ai-sdk/" rel="noopener noreferrer"&gt;article on choosing the right AI SDK&lt;/a&gt; — AI tooling that adapts to your context instead of forcing you to adapt to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agent Skills: Reusable Instruction Sets
&lt;/h2&gt;

&lt;p&gt;Alongside custom agents, Visual Studio now supports agent skills — reusable instruction sets that teach agents how to perform specific tasks. Skills live in &lt;code&gt;.github/skills/&lt;/code&gt; in your repo or &lt;code&gt;~/.copilot/skills/&lt;/code&gt; in your user profile. Each skill is a directory with a &lt;code&gt;SKILL.md&lt;/code&gt; file following the Agent Skills specification.&lt;/p&gt;

&lt;p&gt;When a skill is activated, it appears in the chat so you know it's being applied. The awesome-copilot repo has community-shared skills you can pull in immediately.&lt;/p&gt;

&lt;p&gt;The difference between agents and skills: agents are complete AI personalities with full tool access and model selection. Skills are modular instruction sets that any agent can use. Think of skills as teaching Copilot a technique, while agents are purpose-built specialists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Find_symbol Tool: Language-Aware Navigation
&lt;/h2&gt;

&lt;p&gt;Copilot's agent mode now includes a &lt;code&gt;find_symbol&lt;/code&gt; tool that gives agents language-aware symbol navigation. Instead of searching for text patterns, the agent can find all references to a symbol, access type metadata, understand declarations and scope, and navigate your code using actual language services.&lt;/p&gt;

&lt;p&gt;This is the difference between "find all occurrences of &lt;code&gt;getUserData&lt;/code&gt;" and "find all call sites of this method, understand its signature, and refactor every usage correctly." Supported languages include C++, C#, Razor, TypeScript, and any language with a supported LSP extension.&lt;/p&gt;

&lt;p&gt;For best results, use AI models that support tool-calling. Check out Microsoft's &lt;a href="https://learn.microsoft.com/en-us/visualstudio/ide/copilot-select-add-models?view=visualstudio" rel="noopener noreferrer"&gt;AI model comparison&lt;/a&gt; for details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Live Profiling During Debugging
&lt;/h2&gt;

&lt;p&gt;Performance optimization used to happen after you wrote the code. Now it happens while you're debugging it.&lt;/p&gt;

&lt;p&gt;Visual Studio's new PerfTips feature integrates with the Profiler Agent to show execution time and performance signals inline as you step through code. When you spot a slow line, click the PerfTip and ask Copilot for optimization suggestions on the spot. The Profiler Agent captures elapsed time, CPU usage, and memory behavior automatically, so Copilot's suggestions are based on actual runtime data.&lt;/p&gt;

&lt;p&gt;There's also a new "Profile with Copilot" command in Test Explorer. Right-click a test, select "Profile with Copilot," and the Profiling Agent runs the test, analyzes CPU and instrumentation data, and delivers actionable performance insights. No profiler configuration required.&lt;/p&gt;

&lt;p&gt;This keeps optimization in your regular debugging workflow instead of being a separate investigation phase. I've lost track of how many times I've needed to reproduce a perf issue just to profile it properly — this eliminates that friction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security: Fix Vulnerabilities from Solution Explorer
&lt;/h2&gt;

&lt;p&gt;NuGet package vulnerabilities now surface a "Fix with GitHub Copilot" link directly in Solution Explorer. Click through, and Copilot analyzes the vulnerability, recommends targeted dependency updates, and implements them without disrupting your workflow.&lt;/p&gt;

&lt;p&gt;No more manual vulnerability research or hunting down compatible package versions. You address security issues the moment they're discovered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enterprise MCP Governance
&lt;/h2&gt;

&lt;p&gt;MCP server usage in Visual Studio now respects allowlist policies set through GitHub. Admins can specify which MCP servers are allowed within their organizations. When an allowlist is configured, only approved servers can connect. If you try to use an unauthorized server, you get an error explaining the restriction.&lt;/p&gt;

&lt;p&gt;This is critical for enterprises that need to control which external services process their code. MCP servers are powerful — they can query APIs, access databases, read documentation systems — but that power needs governance. This update provides it.&lt;/p&gt;

&lt;p&gt;If you're building custom agents with MCP connections, this is the compliance layer you need. I covered &lt;a href="https://htek.dev/articles/context-engineering-key-to-ai-development/" rel="noopener noreferrer"&gt;context engineering&lt;/a&gt; as the key to AI-assisted development, and enterprise MCP governance is the security model that makes context engineering viable at scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bookmark Studio: A New Way to Navigate Code
&lt;/h2&gt;

&lt;p&gt;Separately, Mads Kristensen released &lt;a href="https://devblogs.microsoft.com/visualstudio/bookmark-studio-evolving-bookmarks-in-visual-studio/" rel="noopener noreferrer"&gt;Bookmark Studio&lt;/a&gt;, an experimental extension that overhauls Visual Studio's bookmark feature with slot-based navigation, a dedicated manager tool window, labels, colors, folders, and the ability to export bookmarks as plain text, Markdown, or CSV.&lt;/p&gt;

&lt;p&gt;Bookmarks can be assigned to slots 1-9 and jumped to with shortcuts like &lt;code&gt;Alt+Shift+1&lt;/code&gt; through &lt;code&gt;Alt+Shift+9&lt;/code&gt;. Bookmark Studio also tracks bookmarks as text moves during editing, so they stay attached to the relevant code instead of drifting.&lt;/p&gt;

&lt;p&gt;This isn't part of the core Visual Studio release, but it's worth checking out if you navigate large codebases frequently. You can download it from the &lt;a href="https://marketplace.visualstudio.com/items?itemName=MadsKristensen.BookmarkStudio" rel="noopener noreferrer"&gt;Visual Studio Marketplace&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Signals
&lt;/h2&gt;

&lt;p&gt;Visual Studio is betting hard on AI extensibility. Custom agents, agent skills, and enterprise MCP governance are all infrastructure for building specialized AI tooling that fits your workflow. Microsoft isn't trying to build one AI assistant that does everything — they're building a platform so you can build the assistants you need.&lt;/p&gt;

&lt;p&gt;This aligns with the broader shift toward multi-agent systems I've been tracking. GitHub's &lt;a href="https://htek.dev/articles/github-copilot-sdk-agents-for-every-app/" rel="noopener noreferrer"&gt;Copilot SDK&lt;/a&gt; launched recently, and now Visual Studio has the extensibility layer to make custom agents practical for enterprise teams.&lt;/p&gt;

&lt;p&gt;If you're building .NET, C++, or enterprise applications in Visual Studio, this update is worth your time. Download &lt;a href="https://visualstudio.microsoft.com/vs/preview/" rel="noopener noreferrer"&gt;Visual Studio 2026 Preview&lt;/a&gt; and start building custom agents for your team's workflow.&lt;/p&gt;

&lt;p&gt;The era of one-size-fits-all AI assistants is over. Custom agents are here.&lt;/p&gt;

</description>
      <category>devex</category>
      <category>ai</category>
      <category>visualstudio</category>
      <category>productivity</category>
    </item>
    <item>
      <title>GitHub Weekly: Copilot SDK Goes Public, Cloud Agent Breaks Free</title>
      <dc:creator>Hector Flores</dc:creator>
      <pubDate>Wed, 08 Apr 2026 14:13:39 +0000</pubDate>
      <link>https://dev.to/htekdev/github-weekly-copilot-sdk-goes-public-cloud-agent-breaks-free-1h7f</link>
      <guid>https://dev.to/htekdev/github-weekly-copilot-sdk-goes-public-cloud-agent-breaks-free-1h7f</guid>
      <description>&lt;h2&gt;
  
  
  The Week GitHub Stopped Playing Small
&lt;/h2&gt;

&lt;p&gt;This week wasn't incremental polish—it was infrastructure-level change. GitHub released the &lt;a href="https://github.blog/changelog/2026-04-02-copilot-sdk-in-public-preview" rel="noopener noreferrer"&gt;Copilot SDK in public preview&lt;/a&gt;, shipped across five languages, and gave you the same production-tested agent runtime that powers GitHub's own tools. At the same time, &lt;a href="https://github.blog/changelog/2026-03-31-research-plan-and-code-with-copilot-cloud-agent" rel="noopener noreferrer"&gt;Copilot cloud agent shed its PR-only constraint&lt;/a&gt;, unlocking research sessions, implementation plans, and branch-first workflows.&lt;/p&gt;

&lt;p&gt;This matters because GitHub just commoditized agentic orchestration. You no longer need to build your own tool invocation layer, streaming infrastructure, or permission framework—it's yours to embed directly into your applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Copilot SDK: Five Languages, One Runtime
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.blog/changelog/2026-04-02-copilot-sdk-in-public-preview" rel="noopener noreferrer"&gt;Copilot SDK&lt;/a&gt; exposes the same agent runtime powering Copilot cloud agent and Copilot CLI. Instead of reinventing multi-turn sessions, tool calling, and streaming responses, you get production-grade building blocks out of the box.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Now available in:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node.js / TypeScript: &lt;code&gt;npm install @github/copilot-sdk&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Python: &lt;code&gt;pip install github-copilot-sdk&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Go: &lt;code&gt;go get github.com/github/copilot-sdk/go&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;.NET: &lt;code&gt;dotnet add package GitHub.Copilot.SDK&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Java: Available via Maven&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What you get:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Custom tools and agents&lt;/strong&gt;: Define domain-specific tools and let the agent decide when to invoke them&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fine-grained system prompt customization&lt;/strong&gt;: Use &lt;code&gt;replace&lt;/code&gt;, &lt;code&gt;append&lt;/code&gt;, &lt;code&gt;prepend&lt;/code&gt;, or dynamic &lt;code&gt;transform&lt;/code&gt; callbacks to customize sections without rewriting the entire prompt&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Streaming responses&lt;/strong&gt;: Token-by-token streaming for responsive UX&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blob attachments&lt;/strong&gt;: Send images and binary data inline without touching the filesystem&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenTelemetry support&lt;/strong&gt;: Built-in distributed tracing with W3C trace context propagation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Permission framework&lt;/strong&gt;: Gate sensitive operations with approval handlers or mark read-only tools to skip permissions entirely&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bring Your Own Key (BYOK)&lt;/strong&gt;: Use your own API keys for OpenAI, Microsoft Foundry, or Anthropic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The SDK is available to Copilot subscribers and non-Copilot users, including Copilot Free users, and supports BYOK for enterprises. Premium request quota applies when you use Copilot entitlement; BYOK usage is billed through your model provider.&lt;/p&gt;

&lt;p&gt;If you've been building custom agents or &lt;a href="https://htek.dev/articles/github-agentic-workflows-hands-on-guide/" rel="noopener noreferrer"&gt;agentic workflows&lt;/a&gt;, this SDK removes the undifferentiated heavy lifting. You're no longer choosing between building your own orchestration layer or being constrained by GitHub's UI—you can embed Copilot's agentic capabilities directly into your platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cloud Agent Escapes the PR Cage
&lt;/h2&gt;

&lt;p&gt;Until this week, working with Copilot cloud agent meant opening a pull request. That constraint is gone. &lt;a href="https://github.blog/changelog/2026-03-31-research-plan-and-code-with-copilot-cloud-agent" rel="noopener noreferrer"&gt;Copilot cloud agent now supports&lt;/a&gt;:&lt;/p&gt;

&lt;h3&gt;
  
  
  Branch-first workflows
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Copilot generates code on a branch without creating a PR&lt;/li&gt;
&lt;li&gt;Review the full diff before deciding if you're ready for a pull request&lt;/li&gt;
&lt;li&gt;Iterate with Copilot until you're satisfied, then click "Create pull request"&lt;/li&gt;
&lt;li&gt;Want a PR immediately? Just say so in your prompt and Copilot will create one when the session completes&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Implementation plans
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Ask Copilot to produce an implementation plan and review the approach before any code is written&lt;/li&gt;
&lt;li&gt;Approve or provide feedback on the plan before Copilot proceeds&lt;/li&gt;
&lt;li&gt;Once approved, Copilot uses the plan to guide its implementation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Deep research sessions
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Kick off research sessions to have Copilot answer questions requiring thorough investigation&lt;/li&gt;
&lt;li&gt;Ask broad questions about your codebase and get answers grounded in your repository context&lt;/li&gt;
&lt;li&gt;Launch research sessions directly from Copilot Chat&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a fundamental shift in how you interact with AI agents. Pre-approved plans let you validate the approach before committing compute. Branch-first workflows let you iterate without polluting your PR history. Research sessions turn Copilot into a codebase analyst, not just a code generator.&lt;/p&gt;

&lt;p&gt;If you're using &lt;a href="https://htek.dev/articles/agentic-devops-next-evolution-of-shift-left/" rel="noopener noreferrer"&gt;agentic DevOps patterns&lt;/a&gt;, this unlocks new workflows: research → plan → implement → verify, all without creating a PR until you're ready.&lt;/p&gt;

&lt;h2&gt;
  
  
  Organization Controls for Cloud Agent
&lt;/h2&gt;

&lt;p&gt;GitHub shipped &lt;a href="https://github.blog/changelog/2026-04-03-organization-runner-controls-for-copilot-cloud-agent" rel="noopener noreferrer"&gt;organization-level runner controls&lt;/a&gt; for Copilot cloud agent. Previously, runner configuration lived in per-repository &lt;code&gt;copilot-setup-steps.yml&lt;/code&gt; files, making consistent defaults painful.&lt;/p&gt;

&lt;p&gt;Now organization admins can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set a default runner to be used automatically across all repositories&lt;/li&gt;
&lt;li&gt;Lock the runner setting so individual repositories can't override the organization default&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means you can standardize on larger GitHub-hosted runners for better performance or enforce that agents always run on self-hosted runners with access to internal resources. No more repository-by-repository configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Actions Gets Smarter
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.blog/changelog/2026-04-02-github-actions-early-april-2026-updates" rel="noopener noreferrer"&gt;April Actions updates&lt;/a&gt; shipped three meaningful improvements:&lt;/p&gt;

&lt;h3&gt;
  
  
  Service container customization
&lt;/h3&gt;

&lt;p&gt;You can now override entrypoint and commands on service containers using &lt;code&gt;entrypoint&lt;/code&gt; and &lt;code&gt;command&lt;/code&gt; keys in your workflow YAML. The syntax mirrors Docker Compose. This has been a long-standing pain point—no more workarounds.&lt;/p&gt;

&lt;h3&gt;
  
  
  OIDC tokens support custom properties
&lt;/h3&gt;

&lt;p&gt;GitHub Actions OIDC tokens now include repository custom properties as claims (now generally available). This lets you create granular trust policies with cloud providers based on how your organization classifies repositories—environment type, team ownership, compliance tier—without enumerating individual repository names or IDs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Azure private networking VNET failover
&lt;/h3&gt;

&lt;p&gt;Azure private networking for GitHub-hosted runners now supports failover networks in public preview. Configure a secondary Azure subnet (optionally in a different region) so workflows keep running if the primary subnet becomes unavailable. Failover can be triggered manually or automatically by GitHub during a regional outage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Smaller Wins That Matter
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.blog/changelog/2026-04-02-copilot-organization-custom-instructions-are-generally-available" rel="noopener noreferrer"&gt;Organization custom instructions for Copilot&lt;/a&gt; moved to GA for Copilot Business and Enterprise. Organization admins can now set default instructions that guide Copilot's behavior across all repositories. Applied to Copilot Chat on github.com, Copilot code review, and Copilot cloud agent.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.blog/changelog/2026-04-01-github-mobile-stay-in-flow-with-a-refreshed-copilot-tab-and-native-session-logs" rel="noopener noreferrer"&gt;GitHub Mobile refreshed its Copilot tab&lt;/a&gt; with native session logs, in-app controls for agent sessions, and a clearer overview of chat history. You can now view full session logs, create PRs from completed sessions, and stop running sessions—all from your phone.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.blog/changelog/2026-04-02-improved-search-for-github-issues-is-now-generally-available" rel="noopener noreferrer"&gt;Improved search for GitHub Issues&lt;/a&gt; hit GA. Natural language search across issue titles and bodies, hybrid search combining semantic and keyword matching, and API access via REST and GraphQL. When search succeeds, the desired result is in the top three 75% of the time, compared to 66% with traditional search.&lt;/p&gt;

&lt;p&gt;GitHub also renamed the Security tab to &lt;a href="https://github.blog/changelog/2026-04-02-the-security-tab-is-now-security-quality" rel="noopener noreferrer"&gt;Security &amp;amp; quality&lt;/a&gt;, colocating code quality findings alongside security alerts. This lays groundwork for the upcoming GitHub Code Quality GA launch.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Signals
&lt;/h2&gt;

&lt;p&gt;The Copilot SDK release is the real story here. GitHub isn't just building AI-powered tools—it's distributing the infrastructure layer that powers them. That's a fundamentally different strategy.&lt;/p&gt;

&lt;p&gt;By shipping the SDK across five languages with BYOK support, GitHub is betting that embedded agentic capabilities become standard infrastructure, not just features in GitHub's own products. If you're building developer platforms, CI/CD systems, or internal tools, you now have production-grade agent orchestration out of the box.&lt;/p&gt;

&lt;p&gt;Cloud agent's escape from PR-only workflows signals a shift from "AI writes code" to "AI investigates, plans, and implements." Research sessions and implementation plans mean you're validating the approach before committing compute—critical when &lt;a href="https://htek.dev/articles/context-engineering-key-to-ai-development/" rel="noopener noreferrer"&gt;context engineering&lt;/a&gt; determines success.&lt;/p&gt;

&lt;p&gt;The pattern is clear: GitHub is building the plumbing for agentic systems and getting out of the way. The SDK, the unlocked workflows, the organization controls—it's infrastructure for teams to build on, not just features to consume.&lt;/p&gt;

</description>
      <category>github</category>
      <category>devops</category>
      <category>devex</category>
      <category>ai</category>
    </item>
    <item>
      <title>VS Code Weekly: AI Gets an Effort Dial and Nested Subagents</title>
      <dc:creator>Hector Flores</dc:creator>
      <pubDate>Tue, 07 Apr 2026 14:23:13 +0000</pubDate>
      <link>https://dev.to/htekdev/vs-code-weekly-ai-gets-an-effort-dial-and-nested-subagents-2klc</link>
      <guid>https://dev.to/htekdev/vs-code-weekly-ai-gets-an-effort-dial-and-nested-subagents-2klc</guid>
      <description>&lt;p&gt;Microsoft shipped VS Code 1.113 last week—the third release under their new weekly cadence—and it's starting to feel like the editor is settling into a new rhythm. Not just release rhythm, but product rhythm. The features in 1.113 aren't flashy, but they're the kind that make daily work smoother if you're deep in AI-assisted workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Shipped
&lt;/h2&gt;

&lt;p&gt;The headline feature is a &lt;strong&gt;Thinking Effort selector&lt;/strong&gt; right in the model picker. If you're using reasoning models like Claude Sonnet 4.6 or GPT-5.4, you can now dial the reasoning intensity up or down per request—Low, Medium, High—without touching settings. The picker shows your current level (e.g., "GPT-5.3-Codex · Medium") so you always know what you're running.&lt;/p&gt;

&lt;p&gt;This matters because reasoning effort is a cost-quality tradeoff. Need a quick code explanation? Low effort gets you a faster response. Architecting something complex? Crank it to High and let the model think. I've been using this for about a week now, and it's already changed how I prompt. Quick refactors get Low, system design questions get High. Simple, but the fact that it's right there in the UI makes it feel like a different tool.&lt;/p&gt;

&lt;p&gt;The two deprecated settings—&lt;code&gt;github.copilot.chat.anthropic.thinking.effort&lt;/code&gt; and &lt;code&gt;github.copilot.chat.responsesApiReasoningEffort&lt;/code&gt;—are now deprecated, consolidated into the picker. If you have them in your config, clean them up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nested Subagents and MCP Everywhere
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Nested subagents&lt;/strong&gt; are now possible via an opt-in setting: &lt;code&gt;chat.subagents.allowInvocationsFromSubagents&lt;/code&gt;. Before, subagents couldn't call other subagents to prevent recursion hell. Now they can, which unlocks genuinely complex multi-step workflows where one agent delegates to another, just like how you'd hand off tasks to teammates.&lt;/p&gt;

&lt;p&gt;It's powerful but risky—infinite loops are real. Microsoft kept it opt-in for good reason. If you enable it, set clear depth limits in your workflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP server support&lt;/strong&gt; expanded to Copilot CLI and Claude agents. Previously, MCP servers you configured in VS Code only worked for local agents inside the editor. Now they're bridged automatically to CLI and Claude sessions, including workspace-level &lt;code&gt;mcp.json&lt;/code&gt; configs. If you've built custom MCP integrations, this makes them useful everywhere, not just in the editor chat.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Session forking&lt;/strong&gt; also landed for CLI and Claude agents (experimental for CLI via &lt;code&gt;github.copilot.chat.cli.forkSessions.enabled&lt;/code&gt;). You can now branch a conversation at any point to explore different approaches without losing the original context. It's Git branches for AI conversations.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Rest of the Improvements
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Chat Customizations editor&lt;/strong&gt; consolidates all your agent setup—custom instructions, prompt files, agents, skills, MCP servers, plugins—into one place with tabs and embedded code editing. You open it via &lt;code&gt;Ctrl+Shift+P&lt;/code&gt; → "Chat: Open Chat Customizations." If you've been frustrated by scattered settings, this is a relief.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Image viewer&lt;/strong&gt; for chat attachments now has zoom, pan, navigation, and thumbnail strips. No more squinting at tiny previews when you attach screenshots or the agent generates images. It's also available from the Explorer context menu for image files.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;integrated browser&lt;/strong&gt; now supports self-signed certificates. You can temporarily trust them (for one week) during local HTTPS development, just like real browsers. It's a small thing, but it removes the friction of switching to Chrome for SSL testing.&lt;/p&gt;

&lt;p&gt;Microsoft replaced the default themes with &lt;strong&gt;VS Code Light&lt;/strong&gt; and &lt;strong&gt;VS Code Dark&lt;/strong&gt;—cleaner, more modern, and better across screen types. OS theme syncing now defaults to these for new users, so your editor automatically matches your system's light/dark mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Weekly Cadence Means
&lt;/h2&gt;

&lt;p&gt;VS Code 1.111 (March 9) was the first weekly stable release. 1.112 followed on March 18, and 1.113 on March 25. This is a dramatic shift from the old monthly cycle with dedicated Endgame testing weeks. &lt;a href="https://github.com/microsoft/vscode/issues/300108" rel="noopener noreferrer"&gt;Kai Maetzel announced the change on GitHub&lt;/a&gt;, saying quality assurance would fold into continuous delivery instead of a distinct phase.&lt;/p&gt;

&lt;p&gt;Some developers are nervous about the pace—asking whether there's a way to stay behind a few versions for stability. That's understandable. Microsoft is clearly trying to match the iteration speed of competitors like Cursor and Windsurf, which ship continuously.&lt;/p&gt;

&lt;p&gt;But here's the thing: the features in 1.113 feel incremental, not rushed. Nothing in this release screams "we shipped it too early." The Thinking Effort selector is a straightforward UX improvement. Nested subagents are opt-in. MCP bridging is an obvious gap-fill. These are the kinds of improvements you'd expect from a team that's been working on agentic workflows for a while and knows what needs to be better.&lt;/p&gt;

&lt;p&gt;The real test isn't whether they can ship weekly—it's whether they can ship weekly &lt;em&gt;without&lt;/em&gt; regression explosions. So far, so good. But we're only three weeks in.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;If you're using AI agents in VS Code—Copilot, Claude, or custom tools—1.113 makes your life measurably better. The Thinking Effort dial alone is worth the update if you're managing token budgets or just want faster responses for trivial tasks. Nested subagents and MCP everywhere make complex workflows finally practical.&lt;/p&gt;

&lt;p&gt;If you're not using agents, this release is skippable. New themes are nice, but cosmetic. The browser cert trust is a niche fix. Most of the substance here is in the agentic experience.&lt;/p&gt;

&lt;p&gt;I've written before about &lt;a href="https://htek.dev/articles/context-engineering-key-to-ai-development/" rel="noopener noreferrer"&gt;how context engineering matters more than model choice&lt;/a&gt;. The tools in 1.113—session forking, thinking effort control, subagent composition—are all about giving you finer control over that context. You're not just asking the AI a question; you're tuning how it thinks before it answers.&lt;/p&gt;

&lt;p&gt;That's where this is headed. Not bigger models. Better control.&lt;/p&gt;

</description>
      <category>devex</category>
      <category>ai</category>
      <category>opensource</category>
      <category>vscode</category>
    </item>
    <item>
      <title>Copilot CLI Weekly: /fleet Ships — Parallel Multi-Agent Execution</title>
      <dc:creator>Hector Flores</dc:creator>
      <pubDate>Tue, 07 Apr 2026 14:22:42 +0000</pubDate>
      <link>https://dev.to/htekdev/copilot-cli-weekly-fleet-ships-parallel-multi-agent-execution-5730</link>
      <guid>https://dev.to/htekdev/copilot-cli-weekly-fleet-ships-parallel-multi-agent-execution-5730</guid>
      <description>&lt;h2&gt;
  
  
  /fleet: The CLI Gets a Team
&lt;/h2&gt;

&lt;p&gt;The biggest Copilot CLI release in months landed April 1: &lt;strong&gt;&lt;code&gt;/fleet&lt;/code&gt; enables parallel multi-agent execution&lt;/strong&gt;. Instead of working through tasks sequentially, Copilot now dispatches multiple sub-agents to work on different parts of your codebase simultaneously.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.blog/ai-and-ml/github-copilot/run-multiple-agents-at-once-with-fleet-in-copilot-cli/" rel="noopener noreferrer"&gt;GitHub's blog post&lt;/a&gt; describes it best: when you run &lt;code&gt;/fleet Refactor the auth module, update tests, and fix the related docs&lt;/code&gt;, the orchestrator decomposes your task into discrete work items, identifies what can run in parallel, and dispatches independent items as background sub-agents. Think of it as a project lead assigning work to a team.&lt;/p&gt;

&lt;p&gt;This isn't just faster sequential execution — it's fundamentally different architecture. The orchestrator plans, breaks down dependencies, dispatches waves of parallel work, and synthesizes results. Each sub-agent gets its own context window but shares the filesystem. They can't talk to each other; only the orchestrator coordinates.&lt;/p&gt;

&lt;p&gt;The key to making &lt;code&gt;/fleet&lt;/code&gt; work is writing prompts that parallelize well. GitHub's guidance is clear: be specific about deliverables, set explicit boundaries (which files each track owns), and declare dependencies when they exist. Vague prompts lead to sequential execution because the orchestrator can't identify independent pieces.&lt;/p&gt;

&lt;p&gt;You can also reference custom agents defined in &lt;code&gt;.github/extensions/&lt;/code&gt; to assign different models or tools to different tracks. Want a heavy model for complex logic and a light one for boilerplate docs? &lt;code&gt;/fleet Use @technical-writer.md as the agent for all docs tasks and the default agent for code changes.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This is the kind of feature that changes how you think about the tool. I've been covering Copilot CLI since it shipped, and &lt;code&gt;/fleet&lt;/code&gt; is the clearest signal yet that GitHub is building toward a multi-agent development platform. Cross-link to my earlier take on &lt;a href="https://htek.dev/articles/github-agentic-workflows-hands-on-guide/" rel="noopener noreferrer"&gt;agentic workflows&lt;/a&gt; — this is what I was talking about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four Releases in Four Days
&lt;/h2&gt;

&lt;p&gt;Between March 31 and April 3, GitHub shipped &lt;strong&gt;four releases&lt;/strong&gt; (v1.0.14, v1.0.15, v1.0.16, v1.0.17). Here's what matters:&lt;/p&gt;

&lt;h3&gt;
  
  
  Built-in Skills (v1.0.17 — April 3)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/github/copilot-cli/releases/tag/v1.0.17" rel="noopener noreferrer"&gt;Built-in skills&lt;/a&gt; are now included with the CLI, starting with a guide for customizing Copilot cloud agent's environment. This is the first step toward a skill ecosystem that doesn't require manual configuration. Skills are packaged instructions that agents can reference to handle specific tasks or contexts. Expect this to expand rapidly.&lt;/p&gt;

&lt;p&gt;MCP OAuth flows now support HTTPS redirect URIs via a self-signed certificate fallback, improving compatibility with OAuth providers that require HTTPS (like Slack). The &lt;code&gt;/resume&lt;/code&gt; session picker also loads significantly faster with large session histories — a small quality-of-life win that adds up if you're resuming sessions daily.&lt;/p&gt;

&lt;h3&gt;
  
  
  MCP and Permission Hooks (v1.0.16 — April 2)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/github/copilot-cli/releases/tag/v1.0.16" rel="noopener noreferrer"&gt;Version 1.0.16&lt;/a&gt; introduced a &lt;code&gt;PermissionRequest&lt;/code&gt; hook to programmatically approve or deny tool permission requests. If you're building automation workflows with the CLI SDK, this is a big deal — you can now script approval logic instead of relying on interactive prompts.&lt;/p&gt;

&lt;p&gt;MCP tool calls now display the tool name and parameter summary in the timeline, making it easier to follow what's happening when agents invoke external servers. MCP server reconnects also work correctly after directory changes, and BYOK Anthropic providers now respect &lt;code&gt;maxOutputTokens&lt;/code&gt; limits.&lt;/p&gt;

&lt;h3&gt;
  
  
  Export to HTML, OAuth Device Flow, Config CamelCase (v1.0.15 — April 1)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/github/copilot-cli/releases/tag/v1.0.15" rel="noopener noreferrer"&gt;Version 1.0.15&lt;/a&gt; shipped alongside &lt;code&gt;/fleet&lt;/code&gt; with several standout features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/share html&lt;/code&gt;&lt;/strong&gt; exports sessions and research reports as self-contained interactive HTML files. Great for sharing context with teammates who don't have CLI access or for archiving sessions outside of Copilot's storage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Device code flow (RFC 8628)&lt;/strong&gt; as a fallback for MCP OAuth in headless and CI environments. This closes a gap for teams running the CLI in automated pipelines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/mcp auth&lt;/code&gt;&lt;/strong&gt; command with re-authentication UI for MCP OAuth servers, including account switching support. Managing multiple OAuth-protected MCP servers is now a first-class workflow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Config keys now prefer camelCase&lt;/strong&gt; (&lt;code&gt;askUser&lt;/code&gt;, &lt;code&gt;autoUpdate&lt;/code&gt;, &lt;code&gt;logLevel&lt;/code&gt;, etc.). Snake_case still works, but the CLI is standardizing on JavaScript conventions. A small change, but it signals ongoing maturity in the SDK and config system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Copilot mascot now blinks&lt;/strong&gt; with subtle eye animations in interactive mode. Yes, really. It's a delightful detail that reminds you there's a team sweating UX polish even as they ship major features.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The release also includes ~8 UX fixes: autopilot no longer continues after Escape or Ctrl+C, keystrokes typed during loading are no longer lost, the CLI exits immediately after a session ends instead of waiting up to 10 seconds, and Home/End/Page Up/Page Down now work in the diff viewer.&lt;/p&gt;

&lt;h3&gt;
  
  
  27 Fixes and Improvements (v1.0.14 — March 31)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/github/copilot-cli/releases/tag/v1.0.14" rel="noopener noreferrer"&gt;Version 1.0.14&lt;/a&gt; was a cleanup release with 27 bug fixes and stability improvements. Highlights include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Images correctly sent to Anthropic models with BYOM&lt;/li&gt;
&lt;li&gt;Model picker selection now overrides the &lt;code&gt;--model&lt;/code&gt; flag for the current session&lt;/li&gt;
&lt;li&gt;Grep tool handles large files and long lines without running out of memory&lt;/li&gt;
&lt;li&gt;CLI startup time reduced by running terminal detection, auth, and git operations in parallel&lt;/li&gt;
&lt;li&gt;V8 compile cache reduces parse and compile time on repeated invocations&lt;/li&gt;
&lt;li&gt;MCP registry lookups are more reliable with automatic retries and timeouts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nothing flashy, but these are the kinds of fixes that make the tool feel solid in daily use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Copilot Cloud Agent Goes Beyond Pull Requests
&lt;/h2&gt;

&lt;p&gt;On March 31, GitHub &lt;a href="https://github.blog/changelog/2026-03-31-research-plan-and-code-with-copilot-cloud-agent" rel="noopener noreferrer"&gt;announced&lt;/a&gt; that &lt;strong&gt;Copilot cloud agent is no longer limited to pull-request workflows&lt;/strong&gt;. You can now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Generate code on a branch without opening a pull request&lt;/strong&gt;. Review the full diff, iterate with Copilot, and only create a PR when you're ready.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask Copilot to produce an implementation plan&lt;/strong&gt; and review the approach before any code is written. Once approved, Copilot uses the plan to guide its implementation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kick off deep research sessions&lt;/strong&gt; to answer broad questions about your codebase, grounded in repository context.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a significant shift. Copilot cloud agent is transitioning from a "create PR" workflow to a general-purpose coding assistant that works across planning, research, and implementation modes. The CLI's &lt;code&gt;/fleet&lt;/code&gt; feature and the cloud agent's new flexibility are clearly part of the same vision: enabling developers to orchestrate AI agents for complex, multi-phase workflows.&lt;/p&gt;

&lt;p&gt;For context, see my previous coverage of &lt;a href="https://htek.dev/articles/copilot-cli-biggest-week-yet/" rel="noopener noreferrer"&gt;Copilot CLI's biggest week&lt;/a&gt; and &lt;a href="https://htek.dev/articles/top-5-mistakes-creating-custom-github-copilot-agents/" rel="noopener noreferrer"&gt;how to avoid common mistakes with custom agents&lt;/a&gt;. The pieces are coming together.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Signals
&lt;/h2&gt;

&lt;p&gt;Four days, four releases, a major blog post, and a significant expansion of Copilot cloud agent's capabilities. The pace is relentless, and the direction is clear: &lt;strong&gt;GitHub is building a multi-agent development platform&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/fleet&lt;/code&gt; isn't just a parallel execution feature — it's a programming model. You write prompts that decompose into independent tracks, each handled by a sub-agent with its own context. The orchestrator coordinates, but the agents work autonomously. This is the same pattern used in production multi-agent systems, now accessible via a CLI command.&lt;/p&gt;

&lt;p&gt;The built-in skills, permission hooks, MCP OAuth improvements, and cloud agent planning modes all point toward a future where developers compose AI workflows instead of writing code manually. We're not quite there yet, but the foundation is being laid at a rapid pace.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;If you haven't tried &lt;code&gt;/fleet&lt;/code&gt; yet, start with something small — a refactor across multiple files, docs and tests in parallel, or a feature that spans API, UI, and tests. See how the orchestrator decomposes the work. Adjust your prompts based on what parallelizes and what doesn't. You'll quickly learn when &lt;code&gt;/fleet&lt;/code&gt; pays off and when a regular prompt is simpler.&lt;/p&gt;

&lt;p&gt;The CLI shipped ~55 features and fixes across four releases in four days. The tool is maturing fast, and the vision is becoming concrete: a multi-agent platform where developers orchestrate AI teams instead of writing every line themselves. That future is arriving sooner than I expected.&lt;/p&gt;

</description>
      <category>github</category>
      <category>devex</category>
      <category>ai</category>
    </item>
    <item>
      <title>Azure Weekly: Developer Tools Get Smarter, Database Pricing Gets Better</title>
      <dc:creator>Hector Flores</dc:creator>
      <pubDate>Tue, 07 Apr 2026 14:22:35 +0000</pubDate>
      <link>https://dev.to/htekdev/azure-weekly-developer-tools-get-smarter-database-pricing-gets-better-4i2p</link>
      <guid>https://dev.to/htekdev/azure-weekly-developer-tools-get-smarter-database-pricing-gets-better-4i2p</guid>
      <description>&lt;h2&gt;
  
  
  What Shipped This Week
&lt;/h2&gt;

&lt;p&gt;Azure's March releases wrapped with a clear signal: Microsoft is doubling down on making Azure easier to use, cheaper to run, and more aligned with how developers actually build AI-native applications. The Azure Developer CLI got seven updates in one month, database pricing finally caught up to AWS, and .NET Aspire graduated to GA on App Service.&lt;/p&gt;

&lt;p&gt;If you're building cloud-native or AI-enabled apps on Azure, this is the most impactful week since &lt;a href="https://htek.dev/articles/azure-weekly-2026-03-25/" rel="noopener noreferrer"&gt;agentic infrastructure went GA last month&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Azure DevCLI: Local AI Agent Debugging and Copilot Integration
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://devblogs.microsoft.com/azure-sdk/azure-developer-cli-azd-march-2026/" rel="noopener noreferrer"&gt;Azure Developer CLI (azd) shipped seven releases&lt;/a&gt; in March 2026—versions 1.23.7 through 1.23.13. The headline features are all about closing the loop between local development and Azure deployment for AI agents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub Copilot integration in &lt;code&gt;azd init&lt;/code&gt;&lt;/strong&gt;: You can now scaffold new projects with GitHub Copilot assistance directly in the CLI. The flow checks for uncommitted changes before modifying files, prompts for Model Context Protocol (MCP) server tool consent upfront, and guides you through project setup conversationally. When commands fail, azd offers a multi-step troubleshooting flow—explain the error, get guidance, let the agent apply a fix, and retry—without leaving the terminal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Local AI agent debugging&lt;/strong&gt;: This is the big one. You can now run and debug AI agents locally before deploying to Azure. That means full breakpoint debugging, local model testing, and MCP tool validation on your machine. No more deploy-and-pray workflows where you only discover agent behavior issues after pushing to Container Apps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Container App Jobs deployment&lt;/strong&gt;: Azure Container App Jobs are now first-class citizens in azd. Use the existing &lt;code&gt;host: containerapp&lt;/code&gt; configuration—your Bicep template determines whether the target is a Container App or a Container App Job. No extra configuration needed. This matters because batch workloads, scheduled AI agent tasks, and event-driven processing all run better as Jobs than always-on Apps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure preflight validation&lt;/strong&gt;: Before deploying to Azure, azd now validates Bicep parameters and configuration locally. It catches missing parameters and type mismatches without a round-trip to Azure—saving you from failed deployments and unnecessary quota consumption.&lt;/p&gt;

&lt;p&gt;I've been using azd since it launched, and this release fundamentally changes the developer experience. The local-first workflow for AI agents means you're not burning Azure credits to debug basic tool connectivity issues. The GitHub Copilot integration turns "read the docs" into "ask the CLI." And Container App Jobs support finally closes the gap between development templates and production architectures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Azure Savings Plan for Databases: Flexible Discounts That Actually Make Sense
&lt;/h2&gt;

&lt;p&gt;On March 18, Microsoft &lt;a href="https://techcommunity.microsoft.com/blog/finopsblog/announcing-savings-plan-for-databases-flexible-savings-for-modern-evolving-workl/4503107" rel="noopener noreferrer"&gt;announced general availability of the Azure Savings Plan for Databases&lt;/a&gt;. The premise is simple: commit to a fixed hourly spend for one year, and Azure applies up to 35% discounts across your database usage automatically. No region lock, no service lock, no SKU lock.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's covered&lt;/strong&gt;: Azure SQL Database (including Hyperscale and serverless tiers), SQL Managed Instance, Azure Database for PostgreSQL, Azure Database for MySQL, Azure Cosmos DB, and SQL Server on Azure VMs or Arc-enabled servers. SQL Server licensing on VMs is billed at standard rates and not discounted under the plan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why the flexibility matters&lt;/strong&gt;: Traditional Azure Reservations are powerful but punishing if your architecture changes. You lock a discount to a specific service, region, and configuration—great for stable workloads, painful for anything in motion. In practice, most teams operating in 2026 are mid-migration from on-prem SQL Server to Azure SQL, scaling PostgreSQL across new regions, or shifting to Cosmos DB to support AI retrieval patterns. A reservation that penalizes you for evolving is a reservation that fights your roadmap.&lt;/p&gt;

&lt;p&gt;The Savings Plan flips this. You commit a dollar amount per hour, and Azure applies discounts to whichever eligible services you actually run—in whatever configuration, across whatever regions. When your Cosmos DB usage grows at the expense of SQL MI, your savings commitment follows the usage automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Savings Plans and Reservations aren't mutually exclusive&lt;/strong&gt;: Azure applies reservation discounts first (deeper discounts, but restricted to matching configurations). The Savings Plan then picks up any remaining eligible usage not covered by a reservation. For teams with stable workloads in some areas and dynamic growth in others, using both in combination gives maximum coverage.&lt;/p&gt;

&lt;p&gt;This is how AWS Savings Plans have worked for years. Azure's implementation lands with the same flexibility—and it's overdue. For teams carrying multi-database environments or long migrations, this removes the "commit or stay flexible" tradeoff that's been a real friction point in Azure cost management.&lt;/p&gt;

&lt;h2&gt;
  
  
  .NET Aspire on Azure App Service Hits GA
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://azure.github.io/AppService/2026/03/25/Aspire-GA.html" rel="noopener noreferrer"&gt;.NET Aspire on Azure App Service is now generally available&lt;/a&gt;. This makes it easier to take distributed .NET applications from local development to a fully managed production environment on Azure App Service.&lt;/p&gt;

&lt;p&gt;Aspire is Microsoft's opinionated stack for building cloud-native .NET apps—think distributed services, observability, configuration, and service discovery baked in. The GA release means you can now use &lt;code&gt;azd up&lt;/code&gt; to deploy Aspire apps directly to App Service with production-grade support, monitoring, and scaling.&lt;/p&gt;

&lt;p&gt;What changed: App Service now natively understands Aspire's manifest format. Service-to-service communication, dependency injection, and telemetry wiring all work out of the box. You don't need to manually configure service bindings, connection strings, or distributed tracing endpoints.&lt;/p&gt;

&lt;p&gt;If you're building microservices or event-driven architectures in .NET, Aspire on App Service is now the fastest path from &lt;code&gt;dotnet new aspire&lt;/code&gt; to production. It's also a strong signal that Microsoft is betting on opinionated frameworks—not just generic PaaS—as the way forward for developer experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Visual Studio March Update: Build Your Own Custom Copilot Agents
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://devblogs.microsoft.com/visualstudio/visual-studio-march-update-build-your-own-custom-agents/" rel="noopener noreferrer"&gt;Visual Studio March 2026 update&lt;/a&gt; shipped with a custom agent builder for GitHub Copilot. This lets you create specialized Copilot agents tailored to your codebase, workflows, or team conventions.&lt;/p&gt;

&lt;p&gt;The builder supports custom instructions, context injection, and tool definitions. You can create an agent that understands your team's architectural patterns, references your internal docs, or enforces specific coding standards—all within the Copilot interface developers already use.&lt;/p&gt;

&lt;p&gt;This is part of a broader trend: Microsoft is moving from "one Copilot for everyone" to "a Copilot ecosystem you customize." The GitHub Copilot SDK (&lt;a href="https://htek.dev/articles/github-copilot-sdk-agents-for-every-app/" rel="noopener noreferrer"&gt;which I wrote about previously&lt;/a&gt;) already enables this for app developers. Now Visual Studio brings it directly into the IDE for platform teams and DevEx engineers.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It All Means
&lt;/h2&gt;

&lt;p&gt;March's releases have a unifying theme: Azure is investing in developer velocity and cost flexibility—not just raw infrastructure.&lt;/p&gt;

&lt;p&gt;The Azure DevCLI updates are all about local-first workflows and conversational tooling. The Savings Plan for Databases acknowledges that modern cloud architectures are fluid, not static. Aspire on App Service is an opinionated framework play. Visual Studio's custom agents let teams adapt AI tooling to their context.&lt;/p&gt;

&lt;p&gt;These aren't service launches. They're workflow optimizations. Microsoft is betting that what wins developers in 2026 isn't the most compute or the most AI models—it's the platform that makes building, deploying, and operating cloud-native apps the least painful.&lt;/p&gt;

&lt;p&gt;For teams building on Azure, these updates deliver immediate ROI. Local AI debugging cuts iteration time. Database savings plans cut costs without architectural constraints. Aspire cuts boilerplate. Custom agents cut context-switching.&lt;/p&gt;

&lt;p&gt;The question isn't whether these features matter. It's whether your team is using them yet.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>devex</category>
      <category>devops</category>
      <category>ai</category>
    </item>
    <item>
      <title>GitHub Weekly: Actions Gets Serious About Supply Chain Security</title>
      <dc:creator>Hector Flores</dc:creator>
      <pubDate>Tue, 07 Apr 2026 14:22:10 +0000</pubDate>
      <link>https://dev.to/htekdev/github-weekly-actions-gets-serious-about-supply-chain-security-2l45</link>
      <guid>https://dev.to/htekdev/github-weekly-actions-gets-serious-about-supply-chain-security-2l45</guid>
      <description>&lt;p&gt;GitHub dropped one of the most substantial platform updates I've seen in months. The headline: GitHub Actions is getting a full-stack security overhaul. But there's also meaningful movement on agent workflows, Copilot integration across Issues and Slack, and a data usage policy change that's worth understanding.&lt;/p&gt;

&lt;p&gt;Let's break down what shipped and what it means.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub Actions 2026 Security Roadmap: The Big One
&lt;/h2&gt;

&lt;p&gt;GitHub published their &lt;a href="https://github.blog/news-insights/product-news/whats-coming-to-our-github-actions-2026-security-roadmap/" rel="noopener noreferrer"&gt;2026 security roadmap for Actions&lt;/a&gt; this week, and it's not incremental—it's structural. If you run CI/CD on GitHub Actions (and who doesn't), this roadmap signals a major shift toward treating your build infrastructure like the critical attack surface it actually is.&lt;/p&gt;

&lt;p&gt;The context: supply chain attacks targeting CI/CD aren't slowing down. Recent incidents like &lt;strong&gt;tj-actions/changed-files&lt;/strong&gt;, &lt;strong&gt;Nx&lt;/strong&gt;, and &lt;strong&gt;trivy-action&lt;/strong&gt; all followed the same playbook—compromise a dependency, exploit over-permissioned workflows, exfiltrate credentials via unrestricted network access. GitHub's response addresses each layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Three-Layer Security Model
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Ecosystem: Dependency Locking for Workflows&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The biggest change: GitHub is introducing workflow-level dependency locking via a new &lt;code&gt;dependencies:&lt;/code&gt; section in workflow YAML. Think &lt;code&gt;go.mod&lt;/code&gt; + &lt;code&gt;go.sum&lt;/code&gt;, but for your entire Actions dependency graph.&lt;/p&gt;

&lt;p&gt;Today, most workflows reference actions by mutable tags (&lt;code&gt;uses: actions/checkout@v4&lt;/code&gt;). That means what runs in CI isn't deterministic—if a tag gets repointed or a dependency is compromised, the change propagates immediately across every workflow that references it.&lt;/p&gt;

&lt;p&gt;With dependency locking, every direct and transitive dependency gets locked to an immutable commit SHA with cryptographic hashes. Updates show up as reviewable diffs in pull requests. Hash mismatches fail fast before jobs even run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Timeline:&lt;/strong&gt; Public preview in 3-6 months, GA in 6 months.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Attack Surface: Policy-Driven Execution&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;GitHub is adding centralized workflow execution policies built on their &lt;a href="https://docs.github.com/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/about-rulesets" rel="noopener noreferrer"&gt;ruleset framework&lt;/a&gt;. Instead of configuring security per-workflow, you define org-level policies that control:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Who&lt;/strong&gt; can trigger workflows (individual users, roles, trusted apps like Dependabot or Copilot)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Which events&lt;/strong&gt; are allowed (prevent dangerous patterns like &lt;code&gt;pull_request_target&lt;/code&gt; org-wide)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This matters because many CI/CD attacks exploit confusing event behavior or unclear permission boundaries. A centralized policy model means you can block entire classes of attacks without editing thousands of workflow files.&lt;/p&gt;

&lt;p&gt;Policies support &lt;strong&gt;evaluate mode&lt;/strong&gt; first—rules log violations without enforcement, so you can assess impact before flipping the switch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Timeline:&lt;/strong&gt; Public preview in 3-6 months, GA in 6 months.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bonus: Scoped Secrets&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Secrets are getting fine-grained scoping. Instead of repository-wide or org-wide secrets that flow broadly by default, you'll be able to bind credentials to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Specific branches, environments, or workflow paths&lt;/li&gt;
&lt;li&gt;Trusted reusable workflows without requiring callers to pass secrets explicitly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This eliminates implicit secret inheritance and makes trust boundaries explicit. Combined with a permission model change (write access no longer grants secret management by default), this moves Actions toward least-privilege credentials.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Infrastructure: Endpoint Monitoring and Egress Firewall&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;GitHub is treating CI/CD runners as &lt;strong&gt;protected endpoints&lt;/strong&gt; with two new capabilities:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Actions Data Stream&lt;/strong&gt;: Near real-time execution telemetry streamed to your own systems (Amazon S3, Azure Event Hub, Azure Data Explorer). You get workflow execution details, dependency usage patterns, and (future) network activity logs—making CI/CD observable like any other production system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Native Egress Firewall&lt;/strong&gt;: A Layer 7 firewall that operates outside the runner VM. Even if an attacker gains root inside the runner, they can't modify the firewall. You define egress policies (allowed domains, IP ranges, HTTP methods, TLS requirements), and the firewall enforces them.&lt;/p&gt;

&lt;p&gt;The firewall supports two modes: &lt;strong&gt;monitor&lt;/strong&gt; (audit all outbound requests without blocking) and &lt;strong&gt;enforce&lt;/strong&gt; (block anything not explicitly allowed). This gives you a safe adoption path—observe traffic first, build allowlists based on real data, then turn on enforcement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Actions Data Stream timeline:&lt;/strong&gt; Public preview in 3–6 months, GA in 6–9 months. &lt;strong&gt;Native Egress Firewall timeline:&lt;/strong&gt; Public preview in 6–9 months.&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom Images for GitHub-Hosted Runners Hit GA
&lt;/h2&gt;

&lt;p&gt;After a public preview that started in October 2025, &lt;a href="https://github.blog/changelog/2026-03-26-custom-images-for-github-hosted-runners-are-now-generally-available/" rel="noopener noreferrer"&gt;custom images for GitHub-hosted runners&lt;/a&gt; are now generally available.&lt;/p&gt;

&lt;p&gt;This feature lets you start with a GitHub-curated base image and build your own VM image with preinstalled tools, dependencies, certificates, and configurations. The benefit: faster workflow runs, consistent environments, and centralized control over how build environments are standardized and governed at scale.&lt;/p&gt;

&lt;p&gt;If you were already using custom images during preview, nothing changes—your workflows continue working as-is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agent Sessions Surface in Issues and Projects
&lt;/h2&gt;

&lt;p&gt;GitHub shipped &lt;a href="https://github.blog/changelog/2026-03-26-agent-activity-in-github-issues-and-projects/" rel="noopener noreferrer"&gt;agent activity tracking directly into Issues and Projects&lt;/a&gt; this week. When a coding agent (Copilot, Claude, Codex, etc.) is assigned to an issue, its session now appears in the sidebar with live status: queued, working, waiting for review, or completed.&lt;/p&gt;

&lt;p&gt;In Projects, you can toggle on &lt;strong&gt;Show agent sessions&lt;/strong&gt; in table and board views to see agent activity across a large body of work at a glance.&lt;/p&gt;

&lt;p&gt;This is a small but meaningful UX improvement. If you're running &lt;a href="https://htek.dev/articles/github-agentic-workflows-hands-on-guide/" rel="noopener noreferrer"&gt;agentic workflows&lt;/a&gt; at scale, being able to see agent status directly in your planning tools closes a visibility gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Copilot Can Now Edit PRs Directly
&lt;/h2&gt;

&lt;p&gt;Instead of opening a new PR on top of your existing PR, you can now mention &lt;a href="https://github.blog/changelog/2026-03-24-ask-copilot-to-make-changes-to-any-pull-request/" rel="noopener noreferrer"&gt;&lt;code&gt;@copilot&lt;/code&gt; in a pull request&lt;/a&gt; to ask it to make changes directly. Use cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@copilot Fix the failing tests&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@copilot Address this comment&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@copilot Add a unit test covering the case when the model argument is missing&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Copilot works in a cloud-based dev environment, validates changes with your tests and linter, then pushes. If you still want the old behavior (opening a separate PR), just ask: &lt;code&gt;@copilot open a PR to fix the failing tests&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Available with all paid Copilot plans. Business and Enterprise admins need to &lt;a href="https://docs.github.com/en/copilot/how-tos/administer/enterprises/managing-copilot-coding-agent-in-your-enterprise" rel="noopener noreferrer"&gt;enable the coding agent&lt;/a&gt; first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create GitHub Issues from Slack with Copilot
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://slack.com/marketplace/A01BP7R4KNY-github" rel="noopener noreferrer"&gt;GitHub app for Slack&lt;/a&gt; now supports &lt;a href="https://github.blog/changelog/2026-03-30-create-issues-from-slack-with-copilot/" rel="noopener noreferrer"&gt;creating GitHub Issues via natural language&lt;/a&gt;. Mention &lt;code&gt;@GitHub&lt;/code&gt; in any channel, describe the work, and it creates structured issues—titles, bodies, assignees, labels, milestones, even sub-issues.&lt;/p&gt;

&lt;p&gt;Key features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Conversation mode&lt;/strong&gt;: Iterate on issues in a Slack thread before creation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Channel-level instructions&lt;/strong&gt;: Set default working repos with &lt;code&gt;@GitHub settings&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Issue flex pane&lt;/strong&gt;: View created issues directly from Slack&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This builds on the &lt;a href="https://github.blog/changelog/2025-10-28-work-with-copilot-coding-agent-in-slack/" rel="noopener noreferrer"&gt;Copilot coding agent Slack integration&lt;/a&gt; from last October. If your team already lives in Slack, this reduces friction between discussion and tracking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Copilot Data Usage Policy Update: Know What You're Opting Into
&lt;/h2&gt;

&lt;p&gt;On March 25, GitHub announced an &lt;a href="https://github.blog/news-insights/company-news/updates-to-github-copilot-interaction-data-usage-policy/" rel="noopener noreferrer"&gt;update to Copilot interaction data usage&lt;/a&gt;. Starting April 24, interaction data from &lt;strong&gt;Copilot Free, Pro, and Pro+&lt;/strong&gt; users will be used to train and improve AI models unless you opt out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What gets collected&lt;/strong&gt; (if you don't opt out):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Outputs you accept or modify&lt;/li&gt;
&lt;li&gt;Inputs sent to Copilot (code snippets, context around cursor)&lt;/li&gt;
&lt;li&gt;Comments, documentation, file names, repo structure&lt;/li&gt;
&lt;li&gt;Interactions with Copilot features (chat, inline suggestions)&lt;/li&gt;
&lt;li&gt;Feedback (thumbs up/down)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What doesn't get collected&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Copilot Business or Enterprise interaction data&lt;/li&gt;
&lt;li&gt;Data from users who opt out&lt;/li&gt;
&lt;li&gt;Content from issues, discussions, or private repos "at rest" (though Copilot does process private repo code when you're actively using it)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How to opt out&lt;/strong&gt;: Go to &lt;a href="https://github.com/settings/copilot" rel="noopener noreferrer"&gt;settings&lt;/a&gt; → Privacy. If you previously opted out of data collection for product improvements, your preference is preserved.&lt;/p&gt;

&lt;p&gt;This aligns with industry practices (OpenAI, Anthropic, Google all use interaction data for training unless you opt out). GitHub says they've already seen measurable acceptance rate improvements by training on Microsoft employee interaction data, and they believe real-world data improves model quality.&lt;/p&gt;

&lt;p&gt;My take: This is a reasonable policy if you're on Free or Pro and want to contribute to model improvement. But if you work with proprietary codebases or sensitive patterns, verify your opt-out status before April 24. Business and Enterprise users are unaffected.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;GitHub's Actions security roadmap is the story of the week. Dependency locking, centralized execution policies, scoped secrets, and a native egress firewall represent the most significant security investment in Actions I've seen since the platform launched. If you run production CI/CD on GitHub, these changes will fundamentally alter how you think about supply chain risk.&lt;/p&gt;

&lt;p&gt;The agent and Copilot updates are iterative but useful—agent sessions in Issues solve a real visibility problem, PR edits reduce friction, and Slack integration meets teams where they already work.&lt;/p&gt;

&lt;p&gt;The data usage update is notable primarily for transparency. If you care about what happens to your interaction data, now's the time to review your settings.&lt;/p&gt;

&lt;p&gt;GitHub is clearly signaling that 2026 is the year they shift platform infrastructure—especially CI/CD—from flexible-by-default to secure-by-default. The roadmap reflects that. Whether you're ready or not, these changes are coming.&lt;/p&gt;

</description>
      <category>github</category>
      <category>devops</category>
      <category>devex</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
