Files
PaperClipAI/doc/plans/2026-10-12-chat-channel-workflows-refactor.md
T
DottaandPaperclip a2ac5c2715 refactor(chat): isolate Telegram, Slack, GitHub and Discord workflows (#15976)
## Thinking Path

> - Paperclip manages AI agents and their work.
> - Chat channels connect that work to external conversations.
> - The shared chat service still contains provider-specific workflows.
> - Those workflows are hard to inspect in a 34,000-line file.
> - This stack extracts Telegram, Slack, GitHub and Discord workflows
into their channel folders.
> - Shared authorization and service lifetime remain explicit.

## Linked Issues or Issue Description

Refs #15823, #15834, #15836, #15844 and #15845. Root of the second
behavior-preserving channel-folder refactor stack. The dependent PRs
#15979, #15981, #15985 and #15987 have merged into this branch; this PR
lands all five extractions on master.

## What Changed

- Move Telegram maintenance and subscription repair into
`chat-channels/telegram/maintenance.ts`.
- Move Slack session stopping and task starts into
`chat-channels/slack/session-stop.ts` and `task-start.ts`.
- Move GitHub webhook intake and recovery into
`chat-channels/github/webhook-ingress.ts`.
- Move Discord command registration and handling into
`chat-channels/discord/commands.ts`.
- Share type-only runtime records while preserving shared authorization,
transactions, live lease checks and worker ownership.
- Reduce the original service from 34,603 to 29,897 lines, removing
4,706 lines (13.6%).

## Verification

- All 205 selected integration cases passed before and after their
respective extractions: Telegram 41, Slack session stop 2 multi-step
scenarios, GitHub 84, Slack task start 25 and Discord 53. Each dependent
PR records its focused reproduction command.
- Server typecheck, module boundaries and whitespace checks passed.
- Per-slice syntax comparisons passed for moved declarations and
remaining service statements, allowing only the documented live callback
substitutions.
- Tests exercise the public service with disposable databases and
fixture provider transports. They do not claim live third-party
execution.
- Cumulative workspace typecheck and build passed on the final stack.
- Current-head CI passed on the combined stack commit
`d5b37dbe512e3e199932b6608cdc17639eda555a`; Greptile is 5/5 with no
unresolved threads.
- The [original serialized
shard](https://github.com/paperclipai/paperclip/actions/runs/38266518817/job/114870022249)
timed out in unchanged `adapter-auth-signal-routes.test.ts`. The exact
case passed locally without changes, and the [single job
retry](https://github.com/paperclipai/paperclip/actions/runs/38266518817/job/114874019933)
passed. The original timeout remains recorded.

Reproduce the original Telegram slice’s focused suite:

```sh
pnpm exec vitest run server/src/__tests__/chat-channels.integration.test.ts -t 'Telegram durable private draft Stop|Telegram maintenance|Telegram webhook|Telegram.*(pause|remove)|Telegram.*commands'
```

- Cumulative local `pnpm test:run` exited 1: the first server group had
**18,770 passed, 105 skipped, and one timeout** in unchanged
`server/src/services/workspace-git-snapshot-streaming.test.ts:56`
(300,000 ms on macOS). The fail-fast runner did not reach its later
workspace and serialized groups. An unchanged isolated rerun passed
using the existing timeout; this does not turn the original full run
green. The test and its Git utility code are outside this refactor; all
current-head CI jobs passed.
- Reproduce that diagnostic alone: `pnpm exec vitest run
server/src/services/workspace-git-snapshot-streaming.test.ts`.

## Risks

- Credential lease ownership and provider-confirmation ordering must
remain unchanged. Existing rotation, lease-loss, recovery and durable
Stop cases pass.
- The channel factories create no workers or timers and preserve live
runtime, shutdown and gateway-ownership reads.

## Model Used

OpenAI Codex, GPT-6 family, with reasoning and repository tools. The
exact runtime model ID and context-window size are not exposed in this
session. No separate agent was used.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [ ] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-12 12:03:23 -05:00

8.5 KiB

Chat channel workflow refactor, batch two

Base: 37913e4907324838ae2c8c94dc8155002b6d4223 (fresh master after the previous stack merged). The previous five PRs (#15823, #15834, #15836, #15844, #15845) are merged. The service remains 34,603 lines. This batch creates five dependent PRs; merging is separate work.

Order / responsibility Destination Approximate lines moved Contract and ownership Verification
1. Telegram maintenance and Stop subscription repair chat-channels/telegram/maintenance.ts 850 Endpoint lookup, credential lease, credential resolution, runtime fence, live webhook URL, fetch. Own the per-service subscription scan cursor. Preserve claim/confirmation/settlement ordering. Existing maintenance, rotation, lost-lease, subscription recovery and durable draft Stop integration cases before/after.
2. Slack session stopping chat-channels/slack/session-stop.ts 750 Stage and consume durable stop receipts; call shared principal authorization and task-control publication. Preserve captured target, event chronology and transaction locks. Existing Slack stop, stale-session, authorization and failure-recovery cases before/after.
3. GitHub durable webhook ingress and recovery chat-channels/github/webhook-ingress.ts 1,400 Persist verified ingress, replay through the shared webhook entry point, recover provider failures. Keep persistence lease, live shutdown check and current endpoint/credential fences. Existing signed webhook, restart/replay, recovery and stale-source integration cases before/after.
4. Slack task-start admission chat-channels/slack/task-start.ts 920 Recover task starter receipt, revalidate principal/resource/runtime and admit via the shared message path. Preserve lease ownership, synthetic message identity and claim barrier. Existing slash-task concurrency, partial-post and recovery cases before/after.
5. Discord native commands chat-channels/discord/commands.ts 550 Reconcile command registration and admit native controls; shared service retains gateway ownership and authorization. Read gateway ownership live. Existing command registration, stale runtime, gateway ownership and native command cases before/after.

Keep message admission, shared control authority, provider runtime startup, publication transport and service shutdown together. Each factory receives named, typed dependencies. Do not copy mutable maps or snapshot live values. Shared runtime record types may move to a type-only sibling module; do not introduce a runtime import back to the original service.

For each slice: baseline existing public-service tests, mechanical extraction, syntax/body parity, same tests afterward, server typecheck and module boundaries, then PR checks and current-head review. Run broad local gates once at final handoff. Record failures honestly; a retry does not erase the original failure.

Slice 1: Telegram maintenance

  • Service: 34,603 lines / 1,337,182 bytes → 33,718 lines / 1,306,801 bytes.
  • telegram/maintenance.ts: 931 lines / 32,792 bytes; type-only shared runtime records: 38 lines / 1,200 bytes.
  • Twelve moved declarations and six shared types preserve token-level syntax; all remaining service statements are unchanged. The module owns the existing scan cursor and creates no timers or background work. Its live callbacks retain credential-lease checks and runtime fencing around every provider mutation.
  • The same 41 existing Telegram public-service cases passed before and after. Server typecheck, module boundaries and whitespace checks passed. Full local repository gates are deferred to the cumulative handoff; remote CI/review will be recorded in this slice's PR.
  • Reassessment: keep Telegram draft Stop admission and publication transport in the orchestrator. Slack durable session stopping remains the next cohesive cut.

Slice 2: Slack session stopping

  • Service: 33,718 lines / 1,306,801 bytes → 32,928 lines / 1,279,554 bytes.
  • slack/session-stop.ts: 858 lines / 30,166 bytes.
  • All six moved declarations and all remaining service statements preserve token-level syntax. A getter retains the original live heartbeat property access. Shared principal authorization and publication staging stay in the caller; the entire captured-target claim and settlement transactions move.
  • Both existing public-service scenarios passed before and after. They cover exact-run cancellation, retry/deduplication, guest denial, delayed events, queued wakeups, a wakeup promoted after Stop, and cancellation losing to run completion. Server typecheck passes. This factory owns no mutable state.
  • Reassessment: GitHub ingress/recovery has a coherent durable inbox and replay contract; preserve its shutdown getter and persistence compare-and-set lease.

Slice 3: GitHub webhook ingress and recovery

  • Service: 32,928 lines / 1,279,554 bytes → 31,438 lines / 1,225,374 bytes.
  • github/webhook-ingress.ts: 1,579 lines / 58,233 bytes.
  • All 26 moved declarations and remaining statements preserve syntax after the explicit shutdown getter substitution. The replay barrier remains a live getter. Persistence and credential leases remain the same service instances.
  • The same 84 existing public-service cases passed before and after, including authentication before retention, disabled-repository filtering, restart, recovery epochs, concurrent scans, ambiguous redelivery, operator retries, canonical-source changes and stale-runtime replay. Server typecheck passes.
  • Reassessment: Slack task starter confirmation, recovery and failure settlement form one boundary. Keep normal message admission shared and pass its exact existing call contract; do not duplicate provider-visible posts during recovery.

Slice 4: Slack task starts

  • Service: 31,438 lines / 1,225,374 bytes → 30,449 lines / 1,190,643 bytes.
  • slack/task-start.ts: 1,075 lines / 39,262 bytes.
  • Nine moved declarations and all remaining statements preserve syntax. The existing positional message-admission contract is unchanged. The claim barrier remains live through a getter; no worker or mutable per-service state moves.
  • The same 25 public-service cases passed before and after, covering slash-task admission, provider-confirmed recovery without reposting, reclaimed admission, stale credential ownership, ambiguous sends and explicit operator retries. Server typecheck passes.
  • Reassessment: Discord command registration and native control admission can share a channel module. Expose only a live lease-key/token lookup; retain the gateway ownership map, renewal, partial acquisition and shutdown in the service.

Slice 5: Discord commands

  • Service: 30,449 lines / 1,190,643 bytes → 29,897 lines / 1,170,080 bytes.
  • discord/commands.ts: 639 lines / 25,036 bytes.
  • Both moved function bodies and all remaining statements preserve syntax after replacing the map read with a live lease-key/token callback. Gateway map, renewal, runtime identity, partial startup and shutdown ownership stay shared.
  • The same 53 registration and native-command integration cases passed before and after. They cover registration failure, stale/runtime/gateway authority, command receipts, exact target replay and current principal/resource access. Server typecheck passes. Network work remains outside command transactions.

Reassessment after batch two

The service falls from 34,603 lines / 1,337,182 bytes to 29,897 lines / 1,170,080 bytes: 4,706 lines and 167,102 bytes removed (13.6% of source lines). The five provider modules total 5,082 lines, plus 38 shared type lines.

The service function itself spans 27,835 lines. Its 212 nested functions account for 27,146 lines; 51 state/wiring/registration/public API statements account for 435 lines. Imports account for 423 lines. This batch reduces provider workflow mixing; it does not resolve the large shared admission functions.

The largest remaining functions are processMessage (2,166 lines), handleAction (1,074), configureWithCredentialLease (868), processSelectedPublication (729), handleModalSubmit (708), handleWebhook (685), processLifecycleDelivery (657), and applyProviderLifecycleEffect (593). Future candidates include Teams file-transfer authority/protocol and shared inbound persistence/drain ownership. Preserve the shared transaction and live message contracts before attempting those cuts. Stop this batch at five PRs.

Cumulative verification: pnpm -r typecheck and pnpm build passed. Full local test suite results and current-head CI/review are recorded in the final PR.