Back to projects

Draft replies · Tool guard · Local bridge

Discord Auto-Reply for DeepSeek Harness

  • AI
  • Backend

An AI-assisted TypeScript plugin for the DeepSeek Harness desktop app that drafts replies to Discord messages from a persona prompt and a local style file, and leaves each one in the message box for the user to read and send.

delivery:draftMESSAGE_CREATE
Poster drawn from the repository

By the numbers

872lines of TypeScript code
  • npm dependencies0
  • commits, all on one day9
  • commits co-authored with Claude8/9
  • README languages, ban warning at the top3
§ 01

Overview

The plugin connects to the Discord desktop client through a debugging port restricted to the same computer, and follows Discord's own message events through a bridge injected into the page. Each chat has its own harness session on a DeepSeek model, with recent chat context, a persona prompt and a local style file. By default each reply is placed in the message box for the user to read, edit and send; sending automatically is opt-in. Automating a user account is against Discord's Terms of Service and can get the account banned: the project's README says so at the top, and draft mode does not change that.

  • Friends' messages are treated as untrusted input: a guard at harness level refuses every tool call from these sessions, so the model can only answer in text.
  • Messages and images go to DeepSeek through the user's own DeepSeek Harness account, so the model does not run on the local computer. Links are removed first.
  • Works inside the logged-in Discord page and reads or stores no Discord token. It needs Windows, Discord PTB and BetterDiscord.
  • Chat data stays out of the public repository: the style file and session state live in the user's home folder, and the project's rules forbid committing them.
  • Written with AI assistance: Claude is the credited co-author on 8 of the 9 commits, all of which fall on one day.
§ 02

In motion

Friend's messageDeepSeekMessage boxStops hereYou decideEnter
By default the plugin stops at a draft: a person reads it, edits it and presses Enter.
§ 03

How it works

  1. Discord desktop client

    Runs with a debugging port restricted to this computer; a bridge injected into the page reports other people's new messages.

  2. Plugin core

    Answers the open chat by default, removes links, keeps each chat in order and adds earlier messages as context.

  3. Harness session

    One session per chat, with a persona prompt and a local style file. A guard refuses every tool call.

  4. DeepSeek model

    Reached through the DeepSeek account, not run locally: messages and images leave this computer for the model.

  5. Discord message box

    By default the reply goes into that chat's message box or saved draft, and the plugin stops there.

  6. The user

    Decides whether the reply goes out. Automatic sending is opt-in, and either way automating a user account is against Discord's Terms of Service.

§ 04

Engineering notes

Dispatch № 1

Friends' messages must never reach a tool

The problem
Every incoming Discord message becomes a prompt to an agent, on a computer where other plugins attach shell, terminal and memory tools to every session. Friends' messages therefore have to be treated as untrusted input.
The decision
Two layers. Tools are hidden from each Discord session, which also keeps prompts small, and a guard at harness level refuses every tool call from those sessions. The guard is the real boundary, because some agent-scoped tools cannot be hidden.
The result
As the code stands, these sessions can call no tools, so the model's only output is text. The guard dates from the first commit, and the project's notes say never to weaken it. It covers tool calls only; it is not a claim about everything the model can see.

Dispatch № 2

The last step stays with a person

The problem
A reply written by a model would reach a friend from a real person's account. With nobody reading it first, the user would be answering for text they had never seen.
The decision
Delivery defaults to draft: the text goes into the chat's message box and the plugin stops. Automatic sending is a mode the user must switch on, and that one setting also controls the local API's send route, which otherwise returns 403.
The result
By default nothing is posted until the user reads the draft and presses Enter. That is a safety default, not permission: automating a user account is against Discord's Terms of Service, the README warns that it can get the account banned, and draft mode does not change that.

Dispatch № 3

TypeScript with no toolchain, and the assumption that broke

The problem
The project wanted TypeScript and no npm dependencies, and the page bridge has to stay a single expression because it is evaluated inside Discord's page.
The decision
The rewrite relied on Node stripping types at load time, with no build step. GitHub installs land in node_modules, where Node will not do that, so a 25-line build script using Node's own stripper now writes a committed dist folder, and the bridge loads the prebuilt file first.
The result
The package still has no npm dependencies. The costs are that dist must be committed with the sources and that nothing type-checks the code: the types serve editors only.
§ 05

From the source

A draft reaches the message boxpage-bridge.ts
    state.draft = (channelId, text) => {
      const existing: string = drafts.getDraft(channelId, 0) ?? ''
      const addition = existing.trim() ? `\n${text}` : text
      const box = selected.getChannelId() === channelId ? componentDispatch() : undefined
      if (box) {
        // The open message box: type at the cursor, like a paste.
        box.dispatchToLastSubscribed('INSERT_TEXT', { plainText: addition, rawText: addition })
        return 'inserted'
      }
      // Another chat: append to its saved draft, shown when you open it.
      draftActions.changeDraft(channelId, existing + addition, 0)
      return 'drafted'
    }

Draft delivery in full: the text is inserted into the open box or saved to another chat, and no send call appears. Sending is a separate method, used only when send mode is on.

The tool guardindex.ts
  // Friends' messages must never drive tools (shell, files, memory, teammates...).
  // The guard is the hard stop; hiding the tools also keeps prompts small.
  const isDiscordSession = (id: string): boolean => {
    if (state.owned.includes(id)) return true
    try {
      const session = ctx.get('sessions')?.get(id)
      const preset = session ? controller().agents.presetForSession(session) : undefined
      return (typeof preset === 'object' ? preset.id : preset) === config.agentPreset
    } catch { return false }
  }
  ctx.effect(() => ctx.tools.guard(exec => exec.agent !== undefined && isDiscordSession(exec.agent.id)
    ? 'Tools are disabled in Discord auto-reply sessions.' : undefined), 'discord-autoreply: tool guard')

The guard refuses any tool call from a Discord session, recognised by the plugin's own session list or by its agent preset. Hiding the tools is the second layer, a few lines further down the file.

§ 06

Development timeline

No recorded video for this project.

  1. 24 Oct 2026

    READMEs in three languages, with the ban warning at the top

    Milestone
  2. 24 Oct 2026

    Packaged as an installable plugin

    Milestone
  3. 24 Oct 2026

    Rewritten in TypeScript

    Milestone
  4. 24 Oct 2026

    First version

    Milestone