# VS Code Copilot Telegram Hook

This app is the source of truth for the repository's VS Code GitHub Copilot
Telegram notification hooks.

It serves the managed/user-level installation flow:

1. A managed VS Code hook JSON file registered through supported same-host VS
   Code settings targets.
2. A GitHub Copilot CLI user extension that observes the root session lifecycle.

There is intentionally no repository-local `.github/hooks/telegram-notify.json`
workspace hook entry.

The implementation follows the official VS Code Copilot hooks preview behavior:

- The official VS Code hooks docs currently list `~/.claude/settings.json` as
  a default user hook location and `~/.copilot/hooks` as a user hook directory.
- Workspace hooks can be loaded from `.github/hooks/*.json`, but this repository
  does not use a repo-local workspace hook for the Telegram notifier.
- Workspace hooks take precedence over user hooks for the same event when they
  exist.
- The Copilot CLI integration uses its extension API instead of `Stop` hooks,
  because hook pauses do not distinguish intermediate idles from a root session
  that has actually stopped.
- Root lifecycle events are filtered by `agentId`, so subagent completion and
  permission activity do not produce user notifications.
- Managed VS Code hooks suppress observed tool-call session identifiers before
  creating state or sending Telegram messages.
- A root `PreToolUse` event for `ask_user` sends an attention notification with
  the question; other tools and subagent tool calls are ignored.

## Files

- `VSCodeCopilotTelegramHook.csproj`: Native AOT-enabled C# project integrated
  into the monorepo traversal build.
- `Program.cs`: Generic Host bootstrap and command-line entry point.
- `Commands/`: hook lifecycle commands and user-level install/diagnostic
  commands.
- `Notifications/`: Telegram message composition and delivery.
- `State/`: session-scoped `.copilot/notifications/sessions/<safe-session-id>/*.json`
  state management.
- `docs/README.md`: tracks human-authored source inputs and derivation
  relationships for the documentation set in `docs/`.

Only the C# implementation and its managed hook/runtime assets are shipped in
this directory.

## User-level installation

Publish a Native AOT binary for the target runtime from the repository root:

```bash
app=./src/private/app/vscode-copilot-telegram-hook
project="$app/VSCodeCopilotTelegramHook.csproj"

dotnet publish "$project" -c Release -r linux-x64
```

Then install it for the current user:

```bash
app=./src/private/app/vscode-copilot-telegram-hook
binary="$app/bin/Release/net10.0/linux-x64/publish/vscode-copilot-telegram-hook"

"$binary" \
  user install \
  --binary-path "$binary"
```

For headless installation, you can seed missing Telegram values explicitly or
through environment variables:

```bash
app=./src/private/app/vscode-copilot-telegram-hook
binary="$app/bin/Release/net10.0/linux-x64/publish/vscode-copilot-telegram-hook"

"$binary" \
  user install \
  --binary-path "$binary" \
  --telegram-bot-token '<bot-token>' \
  --telegram-chat-id '<chat-id>' \
  --skip-secret-prompt
```

The installer command:

- prompts for the Telegram bot token and chat ID,
- accepts `TG_BOT_TOKEN` and `TG_CHAT_ID` as non-interactive input sources,
- stores missing values in `gopass`,
- asks before overwriting existing stored secrets and defaults to keeping them,
- installs the published Native AOT binary into a user-owned data directory,
- writes a dedicated managed hook JSON file under the install root,
- registers root lifecycle handlers for `SessionStart`, `UserPromptSubmit`,
  `PreToolUse`, and `Stop`,
- installs a GitHub Copilot CLI user extension under the CLI extensions
  directory,
- removes this application's legacy Copilot CLI hook entries to prevent duplicate
  notifications,
- validates before writing side effects that the managed hook file path can be
  represented in VS Code as a supported `~/...` hook location,
- registers that managed hook file in the supported same-host VS Code
  `settings.json` targets through `chat.hookFilesLocations`.

This design follows the manual verification recorded in
[`docs/h-006-human-confirmation-2026-03-14-user-hook-location.md`](./docs/h-006-human-confirmation-2026-03-14-user-hook-location.md)
and the later clarification recorded in
[`docs/h-007-human-confirmation-2026-03-14-same-host-vscode-settings-targets.md`](./docs/h-007-human-confirmation-2026-03-14-same-host-vscode-settings-targets.md)
and intentionally avoids treating `~/.claude/settings.json` as the managed
steady-state install target. By default, the managed hook file lives under the
install root as `vscode-copilot-telegram-hook.hooks.json`. The installer then
registers that managed hook file in the same host's relevant VS Code settings
targets, including the host-local desktop VS Code user settings target and, on
Linux hosts, the VS Code Server Machine settings target even before that server
settings file already exists locally. Because VS Code only accepts relative
paths or `~/...` entries in
`chat.hookFilesLocations`, the installer writes the managed hook registration in
supported `~/...` form rather than as an absolute path.

For GitHub Copilot CLI, the installer writes
`extensions/vscode-copilot-telegram-hook/extension.mjs` under `$COPILOT_HOME`
when set, otherwise under `~/.copilot` on Linux and macOS or
`%USERPROFILE%\.copilot` on Windows. The extension joins the current session,
ignores events with an `agentId`, and notifies only for:

- a root `session.idle` that is not an intermediate autopilot or queued-turn
  boundary;
- an unresolved root permission, elicitation, user-input, plan-approval,
  model-switch, session-limit, or MCP authorization request.

The extension reuses the final root `session.task_complete` summary or
`assistant.message`; it does not make an additional model request. It writes the
event synchronously to `<install-root>/copilot-cli-events/` and starts the
installed native binary as a detached delivery worker. Completion delivery has a
short cancellation window for autopilot continuation, and quickly resolved
attention requests are cancelled before delivery.

Permission-request observation asks once for elevated extension access. If that
access is denied, the extension falls back to normal session access: completion
and other observable lifecycle notifications continue, but permission-request
notifications remain unavailable.

Delivery is best-effort at-least-once. A failed event remains in the spool and is
retried when a later Copilot CLI session loads the extension. A process crash
after Telegram accepts a message but before local cleanup can produce a
duplicate; the implementation intentionally does not add a distributed
idempotency protocol.

If you need to override the default VS Code settings targets, repeat
`--vscode-settings-path` once per target file you want the installer to manage.
If you need to override the default Copilot CLI hook file location, pass
`--copilot-cli-hook-file-path` to `user install`, `user uninstall`,
`user health`, or `user diagnose`. A path whose parent directory is named
`hooks` also selects that directory's parent as the Copilot home used for the
extension path. Other arbitrary paths affect only legacy-hook migration; the
extension remains under the default Copilot home.

If you need to inspect or update stored Telegram secrets after installation,
use the dedicated secret-management command:

```bash
app=./src/private/app/vscode-copilot-telegram-hook
binary="$app/bin/Release/net10.0/linux-x64/publish/vscode-copilot-telegram-hook"

"$binary" user secret
"$binary" user secret --telegram-bot-token '<bot-token>' --telegram-chat-id '<chat-id>'
```

The CLI also provides:

- `user uninstall`: remove the managed installation.
- `user health`: validate the installed binary, VS Code managed hook file,
  Copilot CLI extension, absence of legacy managed CLI hooks, VS Code settings
  registration, and credential resolution.
- `user diagnose`: print the resolved installation paths, VS Code settings
  targets, Copilot CLI hook migration path, extension path, credential
  availability, and log locations.
- `user secret`: read or update the stored Telegram secrets.
- `user test-notification`: send a test Telegram message without waiting for a
  Copilot stop event.
- `hook session-start`, `hook user-prompt-submit`, `hook pre-tool-use`, and
  `hook stop`: internal lifecycle entry points used by VS Code Copilot hooks.

## Logging and troubleshooting

The runtime now writes owner-only diagnostic logs where the operating system
supports Unix file modes:

- Hook logs for valid session-scoped events:
  `.copilot/notifications/sessions/<safe-session-id>/hook.log`
- Fallback hook log for valid payloads that provide `cwd` but lack usable
  session context: `.copilot/hook.log`; fully unparsable JSON only emits
  stderr because the workspace log scope cannot be opened.
- User command log for `user install`, `user uninstall`, `user health`,
  `user diagnose`, `user secret`, and `user test-notification`:
  `<install-root>/user-command.log`

The session-scoped hook log keeps its diagnostics next to the corresponding
session metadata. The log captures hook lifecycle activity, state-file
operations, Telegram send attempts and retries, credential-resolution outcomes,
git probing, and related operational failures. The logger intentionally records
only this application's categories, so external HTTP/resilience components do
not write their request URIs into these files. Secrets such as the Telegram bot
token are intentionally redacted from the log.

Use `user diagnose` to print the current user-command log path, workspace
session log path pattern, workspace fallback hook log path, managed hook file
path, and every targeted VS Code settings path for the directory where you run
the command.

Copilot CLI event files are stored under
`<install-root>/copilot-cli-events/`. Ready files end in `.json`, a worker-owned
claim ends in `.json.working`, and a cancelled delivery has a
`.json.cancelled` marker. Successful, cancelled, partially delivered, and
invalid events are removed. A delivery failure restores the ready `.json` file
for startup recovery. Startup recovery and uninstall cleanup only touch
protocol-owned hash-named event files and extension temporary files; they do not
recursively delete the spool.

During runtime, the hook maintains session-scoped notification protocol state
under `.copilot/notifications/sessions/<safe-session-id>/`, including:

- `session.json`: session metadata for the current Copilot session.
- `current.json`: a cache pointing at the latest Notification Assignment.
- `prompts/<prompt-observation-id>.json`: all observed prompt submissions,
  including observation-only prompts that are not notifiable turns.
- `turns/<notification-turn-id>/turn.json`: a hook-created notifiable turn.
- `turns/<notification-turn-id>/summary.json`: the exact per-turn summary handoff
  file authorized by the Notification Assignment.
- `turns/<notification-turn-id>/stops/<stop-id>.json`: Stop observations.
- `turns/<notification-turn-id>/claims/delivery.claim`: atomic turn delivery claim.
- `turns/<notification-turn-id>/notifications/<notification-key>.json`: durable
  duplicate-suppression records.
- `notifications/<notification-key>.json`: session-level durable records used for
  degraded fallbacks, `ask_user` delivery, and cross-path duplicate suppression.
- `claims/<notification-key>.claim`: atomic delivery claim keyed by the root
  `Stop` timestamp or `ask_user` tool-call identifier hash.
- `hook.log`: always-on diagnostic log for the current Copilot session.

These `.copilot/notifications/sessions/` files are runtime state and should
remain ignored in git.

The gopass prefix is fixed at `copilot/vscode-copilot-telegram-hook` so the
managed VS Code hooks and Copilot CLI extension resolve the same secrets.
As of the current official VS Code docs, the documented default user-level hook
file location is still `~/.claude/settings.json`, even when the feature is used
from VS Code GitHub Copilot. However, manual verification recorded in
[`docs/h-006-human-confirmation-2026-03-14-user-hook-location.md`](./docs/h-006-human-confirmation-2026-03-14-user-hook-location.md)
found that this path only acts as an effective user-level hook source in the
observed environment when `"chat.useClaudeHooks": true` is enabled; leaving the
path enabled in `chat.hookFilesLocations` or explicitly setting it to `true` is
not sufficient by itself. This repository therefore installs a dedicated
managed hook JSON file and registers that file through VS Code settings instead
of relying on Claude settings compatibility. The later clarification recorded in
[`docs/h-007-human-confirmation-2026-03-14-same-host-vscode-settings-targets.md`](./docs/h-007-human-confirmation-2026-03-14-same-host-vscode-settings-targets.md)
also confirms that these settings targets should be treated as distinct
same-host entry points rather than as a cross-machine installation story.
The runtime honors `TG_BOT_TOKEN` and `TG_CHAT_ID` from the process
environment as explicit overrides, but `gopass` is the primary persisted
mechanism for the managed user-level installation.

For VS Code summary generation, the hook emits a Notification Assignment for
high-confidence main user prompts. Only that assignment authorizes writing a
summary, and the agent must write only the exact per-turn `summary.json` path.
The `session_id`, `notification_turn_id`, and `notification_nonce` fields must
match the assignment; `updated_at` must be a valid UTC timestamp; and `summary`
must be non-empty for completed delivery. Write the summary in Chinese when
practical, but a usable non-Chinese summary is allowed. The default `Stop`
behavior never blocks. Valid summaries are sent normally. Truly pending
handoffs, including missing or unreadable `summary.json`, invalid JSON, JSON
`null`, or an exact pending summary for the same Stop, may defer notification
without fallback indefinitely while unresolved. Non-pending invalid, stale, or
ambiguous handoffs produce a degraded fallback notification with durable
duplicate suppression when no pending handoff can satisfy that Stop.

## Build and validation

The project is automatically included in the repository traversal build through
the root `dirs.proj` file because the application lives under `src/` and its
tests live under `tests/`.

Recommended validation steps:

```bash
project=./src/private/app/vscode-copilot-telegram-hook/VSCodeCopilotTelegramHook.csproj
tests=./tests/private/app/vscode-copilot-telegram-hook/Hcoona.VsCodeCopilotTelegramHook.Tests.csproj

dotnet build "$project"
dotnet build "$tests"
dotnet vstest ./tests/private/app/vscode-copilot-telegram-hook/bin/Debug/net10.0/Hcoona.VsCodeCopilotTelegramHook.Tests.dll
dotnet publish "$project" -c Release -r linux-x64
```

## Requirements and documentation map

This README is a project entry point, not the authoritative requirements
ledger. The detailed requirement, research, and provenance material lives under
[`docs/`](./docs/).

Use these documents as the authoritative sources:

- [`docs/README.md`](./docs/README.md): provenance ledger and derivation map.
- [`docs/h-001-original-requirement-brief.md`](./docs/h-001-original-requirement-brief.md):
  original human-authored requirement brief and reference set.
- [`docs/h-002-human-confirmation-2026-03-13.md`](./docs/h-002-human-confirmation-2026-03-13.md):
  later human confirmation of product decisions.
- [h-003 addendum](./docs/h-003-human-confirmation-2026-03-13-addendum.md):
  follow-up human clarification on scope, failure handling, and overlength
  notifications.
- [`docs/h-004-human-confirmation-2026-03-14.md`](./docs/h-004-human-confirmation-2026-03-14.md):
  later clarification that Chinese summaries are best-effort and that runtime
  environment-variable credential overrides are acceptable.
- [`docs/h-006-human-confirmation-2026-03-14-user-hook-location.md`](./docs/h-006-human-confirmation-2026-03-14-user-hook-location.md):
  later manual confirmation that `~/.claude/settings.json` is only effective in
  the observed environment when `"chat.useClaudeHooks": true` is enabled, and
  that managed installation should still prefer an explicitly specified
  separate hook JSON path.
- [`docs/h-007-human-confirmation-2026-03-14-same-host-vscode-settings-targets.md`](./docs/h-007-human-confirmation-2026-03-14-same-host-vscode-settings-targets.md):
  later clarification that the host-local desktop VS Code settings target and
  the VS Code Server Machine settings target belong to the same host for
  managed-installation purposes, and that default installation may target both.
- [`docs/h-008-human-confirmation-2026-03-16-stop-blocking-summary-flow.md`](./docs/h-008-human-confirmation-2026-03-16-stop-blocking-summary-flow.md):
  provenance for removing the managed instruction dependency. Its original
  default `Stop`-blocking recovery direction is superseded by the current
  non-blocking degraded fallback design; blocking recovery is future
  strict/debug scope only.
- [`docs/functional-requirements.md`](./docs/functional-requirements.md):
  current derived functional specification.
- [nonfunctional constraints](./docs/nonfunctional-and-constraints-research.md):
  current non-functional requirements and external constraints research.
- [`docs/vscode-hook-inputs-research.md`](./docs/vscode-hook-inputs-research.md):
  project-focused analysis of the VS Code hook input contract.
- [`docs/implementation-language-evaluation.md`](./docs/implementation-language-evaluation.md):
  implementation-language comparison for PowerShell, Python, and C# based on
  the documented product scope and official platform behavior.

In short, the current product target is a user-level VS Code GitHub Copilot
hook that attempts Telegram delivery for each completed-turn `Stop` event and
includes a concise task summary when available, preferring Chinese on a
best-effort basis, continuing across multiple Telegram messages when needed to
stay within Telegram limits.
