前言¶
DSH(DeepSeek Harness)的思路是一切皆插件。落到 web GUI 上,一個問題很快出現:Chat 模式插件和 Code 模式插件都想佔據會話欄,誰來提供那個「切換模式」的開關?側邊欄裏一排會話長得都一樣,哪些屬於哪個模式、你現在看的是哪一個?
如果註冊表隨某一個模式發行,其他模式就得依賴那個模式的整個包,層疊關係就反了。下面介紹的 omdsh-basemode,做的就是把這個「席位」從各個「姿態」裏拆出來。
這是什麼¶
@omdsh-plugins/omdsh-basemode(插件中心顯示名 Base Mode / 基礎模式)是 DeepSeek Harness web GUI 的會話模式系統,由 omdsh-plugins 維護,MIT 許可證,當前版本 0.2.5,在插件中心的分類爲 system(來自 package.json 的 dsh.plughub.category)。
一句話定位:它是每個模式插件註冊 segment 的註冊表,是渲染這些 segment 的切換器,也是給側邊欄會話按模式上色的圓點。
要強調的是:它自己不發明任何模式。Chat 來自 omdsh-chatmode,Code 來自 omdsh-codemode。它唯一貢獻的姿態是 Work——harness 本身的會話欄,作爲基線註冊進去,讓切換器有地方可以切回去。
核心功能¶
註冊表與切換器¶
sessionModes服務通過ctx.provide提供,是每個模式插件註冊 segment 的入口。- 模式切換器是
shell.overlay(ui-layout 的全框架浮層)裏的一個條目,居中於會話欄,無人使用時自動隱藏。 - 註冊表強制同一時刻恰好一個 segment 處於 active:標記一個 active 會清除其餘,貢獻者通過自己的條目變 false 得知失去了會話欄。
基線姿態 Work¶
- 通過
registerBaseline註冊,就是 harness 自身的會話欄,使用與 omdsh-chatmode 相同的名稱、文案、顏色與圖標。 - 任何聲明
fallback或佔用其 id 的註冊會讓它讓位;那個插件卸載後,基線恢復。 - 它的 active 標誌是派生值:恰好在其在位且沒有貢獻者接管時持有會話欄。
- 基線單獨存在時不渲染切換器——單 segment 的控件無從切換。因此只有模式系統而沒有模式插件的 profile,什麼都不顯示。
側邊欄標記¶
- 通過
row-marks.ts在側邊欄每行繪製模式色圓點:直接畫在既有的行上,由註冊表的tone與owns驅動。 - 在會話欄正在展示的會話上繪製側邊欄高亮,僅在會話欄與選中項不一致時寫入。
New Session 的去向¶
- 覆寫
workspaces.startSession:New Session 請求先交給持有會話欄的活動 segment。 - 無人接手時公告,並新建一個真正的新會話,而不是複用 workspace 舊的空白會話。
sessionModes.column報告會話欄實際顯示的內容:活動 segment 聲明瞭 scope 就用它,否則是選中的會話。
對 harness 本體的邊界¶
- 不修改 harness 本體:註冊槽位是公開席位,覆寫是被自有屬性遮蔽的原型方法,撤回行時兩者都歸還。
- 不註冊 settings namespace:segment registry 沒有可配置項,插件中心卡片沒有表單。這是刻意的,不是遺漏。
爲什麼拆成獨立的包¶
它曾屬於 Chat mode,後來拆分爲獨立包,以消除層疊倒置。模式插件都需要註冊表;如果註冊表內置於某一個模式,其他模式就被迫依賴那個模式的整個包。拆分之後依賴是誠實的:每個模式插件依賴本包,本包不依賴任何模式。
安裝與啓用¶
本次覈對的資料中沒有官方安裝命令原文,本文不做拼湊。安裝方式請以倉庫 README 與目錄頁爲準:
- GitHub 倉庫:https://github.com/omdsh-plugins/omdsh-basemode
- 目錄頁:https://www.skillhub.cn/plugins/omdsh-plugins/omdsh-basemode
從 package.json 可以確認的環境信息:node ^22.19.0 || >=24.0.0,包管理器 pnpm@11.7.0;客戶端側 platform 爲 web,依賴注入 @deepseek-ai/dsh-client-ui-layout、@deepseek-ai/dsh-client-locale、@deepseek-ai/dsh-api-session-controller、@deepseek-ai/dsh-api-workspace-controller。
需要說明:本文依據的 README 與 package.json 在「The contract」一節附近被截斷,其後可能存在的安裝與配置章節未能覈對,以倉庫爲準。
模式插件如何接入¶
這一節面向要寫模式插件的讀者。先拿到服務,再註冊 segment:
// 不要寫在頂層 inject 裏——見規範第 9 條
ctx.inject(['sessionModes'], (mctx) => {
const modes = mctx.get('sessionModes') as SessionModes | undefined
if (modes === undefined) return
mctx.effect(() => modes.register({
id: 'code',
order: 20,
label: t('mode.code'),
hint: t('mode.code.hint'),
tone: 'var(--dsw-alias-state-error-primary)',
icon: createElement(IconCodeOutline16, { size: 14 }),
owns: isCodeSessionId,
available: true,
enter: () => { /* 按下後執行的導航 */ },
newSession: (workspaceId) => { /* 該模式開啓了新會話時返回 true */ },
}))
})
經過上面的步驟,segment 就進入了註冊表。有四條契約值得逐條記住:
1、類型只用 type 導入。SessionModes 從 @omdsh-plugins/omdsh-basemode/client 以 import type 導入,絕不作爲值導入。跨插件的 VALUE 導入要麼把本包運行時內聯進你的 bundle,要麼向 shell 的凍結模塊表索要它答不上的 specifier,client bundle purity gate 會因此使構建失敗。所以上面的服務用字符串 'sessionModes' 解析,而不是本包導出的 SESSION_MODES 常量——服務名是和運行時共享的線上名字,不是和包共享的符號。
2、同一時刻恰好一個 segment active。這是註冊表強制的,不靠貢獻者自覺:你標記自己 active 會清除其餘;你看到自己的條目變 false,就是失去了會話欄。
3、文案帶進來就是本地化的。切換器按原樣渲染 label、hint、unavailableHint,自身只擁有兩個詞(switch.aria,以及模式不可用且未說明原因時的 fallback)。在 locale/change 時重新 update segment 以保持文案本地化。
4、enter 是一次導航,不是狀態寫入。按下之後由 segment 把世界變爲真——打開會話、開始一個,或接管會話欄——active 標誌由它自行上報。任何東西都不跨 reload 記憶模式。
兩個字段的語義:owns 回答「這個會話是不是我的」;newSession 返回 true 表示該模式已開始一個新會話。
適用場景與注意¶
適合誰:
- 要向 DSH web GUI 貢獻會話模式的插件作者,這裏是必經的註冊入口。
- 想自由組合模式的 profile 使用者。注意本包不發明模式,只裝它不會出現任何模式按鈕。
注意事項:
- segment registry 沒有可配置項,不要在插件中心卡片裏找表單。
- 插件以當前 dsh 進程權限運行,安裝前請檢查源碼與許可證(本包爲 MIT)。
- 星標數與官方安裝命令在本次覈對中缺失,README 亦有截斷,最新契約說明以倉庫爲準。
結尾¶
omdsh-basemode 做的事很剋制:一個公開的註冊席位,一個居中且自動隱藏的切換器,側邊欄的圓點與高亮,外加一條 New Session 的分流規則。模式本身不歸它管——Chat 與 Code 各自來自自己的插件,Work 是 harness 原有的會話欄。需要給 DSH web GUI 增加會話模式時,從這裏註冊。
- GitHub 倉庫:https://github.com/omdsh-plugins/omdsh-basemode
- 社區目錄頁:https://www.skillhub.cn/plugins/omdsh-plugins/omdsh-basemode (獨立站點,與 DeepSeek / 幻方無官方從屬關係)