返回項目列表

回覆草稿・工具防護・本機橋接

DeepSeek Harness Discord 自動回覆外掛

  • 人工智能
  • 後端

一個在 AI 協助下以 TypeScript 編寫的 DeepSeek Harness 桌面應用程式外掛,按人設提示及本機風格檔草擬 Discord 回覆,並將每則回覆留在訊息輸入框,由用戶閱讀後自行發送。

delivery:draftMESSAGE_CREATE
據程式庫內容繪製的海報

數字紀錄

872行 TypeScript 程式碼
  • 個 npm 依賴套件0
  • 次提交,全部在同一天9
  • 次提交與 Claude 共同撰寫8/9
  • 種語言的 README,開頭均載封禁警告3
§ 01

概覽

外掛透過僅限本機的除錯連接埠接駁 Discord 桌面用戶端,並以注入頁面的橋接程式接收 Discord 本身的訊息事件。每個聊天室各有一個使用 DeepSeek 模型的 Harness 工作階段,並附上近期對話內容、人設提示及本機風格檔。預設情況下,每則回覆都會放在訊息輸入框,由用戶閱讀、修改後自行發送;自動發送須自行開啟。自動化操作用戶帳戶違反 Discord 服務條款,帳戶亦可能因此被封禁:項目的 README 在開頭已清楚列明,草稿模式亦不會改變這一點。

  • 朋友的訊息一律視為不可信輸入:工具防護會在 Harness 層面拒絕這些工作階段的所有工具呼叫,模型只能以文字回應。
  • 訊息及圖片會經用戶本人的 DeepSeek Harness 帳戶傳送至 DeepSeek,模型並非在本機運行。連結會先行移除。
  • 在已登入的 Discord 頁面內運作,不會讀取或儲存 Discord 權杖。須配合 Windows、Discord PTB 及 BetterDiscord 使用。
  • 聊天資料不會進入公開程式庫:風格檔及工作階段狀態存放於用戶的主資料夾,項目規則亦禁止提交這些內容。
  • 在 AI 協助下編寫:9 次提交中有 8 次列明 Claude 為共同作者,全部提交均在同一天完成。
§ 02

動態摘要

朋友的訊息DeepSeek訊息輸入框到此為止由你決定Enter
預設情況下,外掛只留下草稿:由用戶閱讀、修改,再按下 Enter。
§ 03

運作方式

  1. Discord 桌面用戶端

    以僅限本機的除錯連接埠運行;注入頁面的橋接程式會回報其他人傳來的新訊息。

  2. 外掛核心

    預設只回應當前聊天室,並移除連結、按聊天室維持訊息次序,以及附上較早的訊息作為上下文。

  3. Harness 工作階段

    每個聊天室一個工作階段,配合人設提示及本機風格檔。工具防護會拒絕所有工具呼叫。

  4. DeepSeek 模型

    經 DeepSeek 帳戶存取,並非在本機運行:訊息及圖片會離開這部電腦,傳送至模型。

  5. Discord 訊息輸入框

    預設情況下,回覆會放入該聊天室的訊息輸入框或已儲存的草稿,外掛的工作亦至此為止。

  6. 用戶

    由用戶決定是否發出回覆。自動發送須自行開啟;無論採用哪種方式,自動化操作用戶帳戶均違反 Discord 服務條款。

§ 04

工程札記

札記第 1 則

朋友的訊息絕不能觸及工具

問題
每則收到的 Discord 訊息都會成為代理程式的提示,而同一部電腦上的其他外掛會為每個工作階段加入 Shell、終端機及記憶工具。因此,朋友的訊息必須一律視為不可信輸入。
決定
設置兩層防線。先向每個 Discord 工作階段隱藏工具,同時令提示保持精簡;再由 Harness 層面的防護拒絕這些工作階段的所有工具呼叫。部分工具屬代理程式範圍,無法隱藏,故真正的安全邊界是這道防護。
結果
按現有程式碼,這些工作階段無法呼叫任何工具,模型只能輸出文字。防護自首次提交起已經存在,項目文件亦註明不得削弱。它只涵蓋工具呼叫,並不代表模型所見的一切內容均受限制。

札記第 2 則

最後一步留給用戶

問題
回覆由模型撰寫,卻會以真人的帳戶傳給朋友。若無人先行過目,用戶便要為自己從未看過的文字負責。
決定
傳遞方式預設為草稿:文字放入該聊天室的訊息輸入框後,外掛即告停止。自動發送須由用戶自行開啟,而同一設定亦控制本機 API 的發送路由,未開啟時一律回傳 403。
結果
預設情況下,用戶閱讀草稿並按下 Enter 之前,不會發出任何內容。這只是安全預設,並非許可:自動化操作用戶帳戶違反 Discord 服務條款,README 亦警告帳戶可能因此被封禁,草稿模式不會改變這一點。

札記第 3 則

沒有工具鏈的 TypeScript,以及失效的假設

問題
項目希望使用 TypeScript 而不依賴任何 npm 套件,而頁面橋接程式須在 Discord 頁面內求值,因此必須維持為單一表達式。
決定
重寫時依靠 Node 在載入時剝離型別,不設建構步驟。但從 GitHub 安裝的套件會放在 node_modules,Node 不會在該處剝離型別;於是改用 25 行建構腳本,以 Node 內置的剝離功能生成 dist 資料夾並一併提交,橋接程式則優先載入預先建構的檔案。
結果
套件仍然沒有任何 npm 依賴。代價是 dist 須與原始碼一併提交,而且沒有任何工具檢查型別:型別只供編輯器使用。
§ 05

原始碼摘錄

草稿進入訊息輸入框page-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'
    }

這就是草稿傳遞的全部:文字插入當前的輸入框,或儲存至其他聊天室,當中沒有任何發送呼叫。發送是另一個方法,只在開啟發送模式時使用。

工具防護index.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')

工具防護會拒絕 Discord 工作階段的任何工具呼叫,並按外掛自身的工作階段清單或代理預設加以識別。隱藏工具是第二層防線,就在檔案下方數行。

§ 06

開發時間線

此項目沒有錄影片段。

  1. 2026年10月24日

    推出三語 README,並於開頭載明封禁警告

    里程碑
  2. 2026年10月24日

    封裝為可安裝外掛

    里程碑
  3. 2026年10月24日

    以 TypeScript 重寫

    里程碑
  4. 2026年10月24日

    首個版本

    里程碑