## 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>
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.