本文定义 dsh-advisor 暴露的消费者表面:(1) 包根库 API(import { … } from 'dsh-advisor');(2) 客户端入口(dsh-advisor/client,web 注入的 Advisor 卡片);(3) /advisor 指令面(按会话控制)。安装 → docs/install.zh.md;发布流程 → docs/release.md。
契约边界:本文描述的是本包的导出表面与生命周期。有效的包契约 ≠ 集成完成的下游仓库 —— 集成是否完整必须以目标仓库里的实际 wiring 为准。
src/index.ts 是 cordis 插件入口(bundle 组合包的宿主半),从包根统一导出:
import { name, inject, Config, apply } from 'dsh-advisor'
import type { AdvisorConfig, ResolvedAdvisorConfig } from 'dsh-advisor'| 导出 | 类型 | 说明 |
|---|---|---|
name |
'dsh-advisor'(string 常量) |
插件名。 |
inject |
['sessions', 'agents', 'llm'] |
插件消费的服务;行在全部可用后加载。 |
Config |
schemastery schema(value) | Loader schema(严格:默认值 + 类型/边界校验),由 cordis Loader 校验插件行 config。见 配置指南。 |
AdvisorConfig |
type | 插件行 config 契约(provider / model / systemPrompt / immuneTurns / maxDeltaMessages——enabled 键已移除,2026-09-26:插件行开关即总开关)。 |
ResolvedAdvisorConfig |
type | 显式模型门禁(S4)之后的运行时契约(含可选的 disabledReason)。 |
apply(ctx, config) |
function | 插件 apply:为 entry 的 volatile live 字段建立 settings bridge(loader/volatile-update 驱动)、注册 AdvisorConfigGateway 与 typert 端点(条件 ctx.inject(['typert'], ...))、组合 observer / runtime / delivery、在组合 command registry 时注册 /advisor 指令(条件 ctx.inject(['commands'], ...))。 |
包根没有按函数粒度重导出内部运行函数(如
resolveAdvisorConfig不在包根导出面内 —— 它由src/config.ts内部使用;包根的运行时导出面就是name/inject/Config/apply,类型面是AdvisorConfig/ResolvedAdvisorConfig)。这与按「纯函数库」设计的插件不同 ——dsh-advisor是组合包(bundle),不是函数库;内部模块(src/advisor-runtime.ts、src/delivery.ts、src/commands.ts等)是 cordis-free 的实现单元,通过apply的 wiring 消费,不在包根暴露。
| 入口 | 解析 |
|---|---|
dsh-advisor(.) |
lib/index.js(types lib/index.d.ts)—— 宿主半插件入口 |
dsh-advisor/client(./client) |
lib/client.js(types lib/client.d.ts)—— 浏览器半(web 卡片) |
dsh-advisor/package.json |
元数据 |
发布物(files)为 lib/ + cordis.patch.yml + scripts/;cordis.patch.yml 插入一行插件配置 —— id: advisor,name: dsh-advisor。运行时依赖全部声明为 peerDependencies(@deepseek-ai/cordis / @deepseek-ai/schemastery / @deepseek-ai/dsh-* / react),由 dsh 安装的扁平 profile module fallback 解析。
apply 在 try/catch 中构造 AdvisorConfigGateway(src/gateway.ts),它以 cordis 服务键 'advisor' 注册(TypertRemoteService 基类)。这是 /api/advisor/* RPC 端点的调度目标(typertGateway 经 ctx.get('advisor') 分发)—— 不是面向消费者的公共 API:它不暴露可调用的纯函数面,也没有稳定对象契约可依赖。跨插件需要读取 advisor 状态时,应使用文档化的面(/api/advisor/get、/advisor status),而不是读取该服务对象的内部。
| 端点 | 作用域 | 说明 |
|---|---|---|
/api/advisor/get / /api/advisor/set |
全局 | entry config 的读取/写入(经硬门禁的 resolved 值)。 |
/api/advisor/getSession(B2) |
会话 | 返回权威会话快照 { sessionId, enabled, modelOverride?, modelSource?, effectiveModel?, disabledReason?, lifetime: 'live-session' }(缺省键在 wire 上省略)。只读,不分配状态。 |
/api/advisor/setSessionModel(B2) |
会话 | { sessionId, selection: { provider, model } | null }:钉住/更新原子对;selection: null = reset(重新继承当前全局默认,从不触碰启用开关)。返回提交后的同一快照。写入经同一 AdvisorCommandController(resolveModelInfo 60 秒校验、按会话 generation 栅栏、路由变更语义继承自指令面)。 |
业务结果(未知/已销毁会话、无 elected owner、校验拒绝/失败、被更新/取消)以插件域错误标签在返回数据中表达({ error: { tag, message } },advisor/session-unknown / advisor/unavailable / advisor/rejected / advisor/failed / advisor/superseded / advisor/cancelled)—— 不抛新的 coded 失败、不扩展 dsh 的失败词汇。未知/已销毁的会话目标在校验开始之前拒绝(不为死目标分配状态);SessionId 不是授权——请求仍先经过 Connection 的 Host/Origin + 浏览器认证边界;无裸 HTTP 端点、无自创会话 ACL。两个会话端点由获得 reviewer 角色的 fiber的控制器提供(懒解析的 session face);持有 advisor 服务键但无 elected owner 的 fiber 对其回答 advisor/unavailable 并保持惰性(无自动晋升)。
多 fiber 去重:宿主会组合多个 dsh-advisor fiber(观察到的典型情况是 3 个)。advisor 服务键的注册是「先注册者拥有」,后续 fiber 静默回退(不报错、不重复 wiring;settings bridge 无注册动作——每个 fiber 自己的 bridge 由 loader/volatile-update 的 owning-fiber 过滤天然隔离);typert 端点注册同理(重复注册失败时该 fiber 不提供端点)。首个获得 reviewer 角色的 apply 负责 observer / runtime / delivery 与 /advisor 指令的 wiring(单评审者守卫,src/index.ts claimReviewer)。生命周期:所有注册都是 fiber 作用域 effect —— fiber dispose 后端点 / reviewer 声明随之撤销,后续 re-apply / re-mount 可接管。
src/client/index.ts 是浏览器半,把 Advisor 卡片注册进宿主声明的 plugins.bundle.config 卡片 slot(web「插件」页上 dsh-advisor 组合包自己的页面,bundle key dsh-advisor),并(B2)把会话级 Advisor 动作注册进宿主声明的 conversation.session.header.actions 会话作用域 list slot——该动作绑定到插槽父级解析的 SessionId,是 /advisor model 的 web 对应面(显示有效 pair/来源/live-session 生存期,支持钉住与 reset);全局卡片保持仅全局。会话动作只调用会话端点,控制面不可用时不提供写入、也绝不回退到全局写通道;打开时与断连/聚焦信号时刷新,无推送事件、无轮询,关闭态标签为中性(不显示模型名):
import type { AdvisorCardProps, AdvisorSettingsStore, ModelOption, ProviderOption } from 'dsh-advisor/client'
import type { AdvisorSessionActionProps, AdvisorSessionModelController, AdvisorSessionMenuState, AdvisorSessionSnapshotView } from 'dsh-advisor/client'inject:['slots', 'locale', 'connection', 'settingsSchema'](cordis fiber 注入;settingsSchema为 ui-settings 提供的不可变路径写入服务);locale 字典命名空间settings.advisor(zh / en);- 类型导出:
AdvisorCardInjected、AdvisorCardProps、AdvisorKey、AdvisorDraft、AdvisorSettingsState、AdvisorSettingsStore、ApplyFailure、ApplyState、ModelOption、ModelsEmptyReason、ProviderOption;B2 会话面:AdvisorSessionActionInjected、AdvisorSessionActionProps、AdvisorSessionMenuState、AdvisorSessionModelController、AdvisorSessionRpcPayload、AdvisorSessionSelection、AdvisorSessionSnapshotView; - value 导出:
refreshIfLoaded(纯 controller 辅助:仅在卡片首次加载后重取页面快照;未打开的卡片不在后台失效时发起 fetch); - web 注入声明(
package.jsondsh.client):@deepseek-ai/dsh-client-store+@deepseek-ai/dsh-client-ui-plugin-manager+@deepseek-ai/dsh-client-locale,平台web; - 导入纯度边界:客户端 half 只 value-import 冻结的平台模块表(
CLIENT_EXTERNALS:react /@deepseek-ai/cordis/ ui-slots / ui-primitives /@deepseek-ai/dsh-client-store);其余@deepseek-ai/*全部 type-only(构建期擦除),值经 cordis 注入到达(含settingsSchema服务)。
卡片的数据面(src/client/advisor-store.ts):
- advisor 配置:只经网关 RPC 通道(
connection.rpc.call('/api', 'advisor/get' | 'advisor/set', …));get返回{ config }(宿主硬门禁后的 resolved 值,缺省键在 wire 上省略),set接受{ patch }并返回新合成值; - 会话级模型面(B2):
AdvisorSessionModelController(每个会话作用域绑定一个实例,由插槽 inject 工厂构造、渲染器按 entry × 会话绑定 memoize)只调用/api/advisor/getSession|/api/advisor/setSessionModel,每次调用携带自己的sessionId且只提交进自己的 store——旧绑定的迟到响应既改不到别的会话,也会被同会话的请求栅栏整体丢弃; - provider / model 目录:走
api.settings.describe/api.llm.*(llm-*命名空间在 exposed 集合内);configured provider = 命名空间 + profile 均解析(KD-S2),model 选项 = profile 声明模型优先、catalog 回退;会话动作以只读方式复用同一目录 store; - 保存时对草稿与上次读取的配置做 diff,只发送变更键为 patch;清空 provider/model 存显式
''(网关 merge 无法表达 unset,解析器把''当缺失);advisor.get失败 → 卡片显示 config-channel 提示而非可写表单(KD-G5),永不提供 Apply。
/advisor 指令在组合了 command registry(commands 服务)时经条件 ctx.inject(['commands'], ...) 子项注册(src/commands.ts registerAdvisorCommands)—— 无 registry 的 headless / standalone 组合静默不注册。解析器 parseAdvisorCommand(src/commands.ts)接受以下形式:
/advisor toggle the advisor for this session
/advisor on enable the advisor for this session
/advisor off disable the advisor for this session
/advisor status show state, effective model + source, runtime status, pending count, last activity
/advisor model show the effective reviewer model, its source (session|global), and the live-session lifetime
/advisor model set <provider> <model> pin a reviewer model for the invoking session only
/advisor model reset drop the session pin and re-inherit the current global defaults
(其它输入) → usage 文本
- 会话级且临时:
on/off/toggle翻转的是按会话的启用 override(AdvisorSessionOverrides,override ?? true——运行中的插件行即启用),从不修改持久化配置;/advisor on开启一个 config 缺少provider/model的会话不会发起模型调用 —— 回复与/advisor status都会显示 S4 门禁原因; - 会话级模型覆盖:
model set/model reset读写同一会话级机制内的运行时modelOverride: { provider, model }原子对(生存期、解析顺序、校验细节 → 配置指南 · 会话级评审模型覆盖)——同样从不写入持久化配置;set只作用于发起调用的会话(无 session-id 参数,模型 id 可含/),提交前经resolveModelInfo校验(60 秒、可取消、无自动重试);reset重新继承当前全局默认且从不触碰启用开关; /advisor on是手动恢复路径:恢复quota_exhausted(KD-5 无自动恢复定时器)并全新重建halted(永久性模型错误)的会话 runtime;开启时把 observer 游标 seed 到当前 transcript 长度(KD-5 seed-on-enable,不做全史重放);/advisor status状态面(src/commands.tsAdvisorSessionStatus+advisorStatusText):enabled(有效开关)、disabledReason(S4 门禁阻挡时)、provider/model(有效路由——会话覆盖优先于全局默认——即使禁用也显示)、modelSource(session|global)、runtimeStatus(running|paused|quota_exhausted|halted|disabled)、pendingCount(待 drain 的 delta 数)、lastActivityAt(最后一次 accepted-note 的 ISO 时间,之前为never)。/advisor config保持全局默认值回读(会话级覆盖不改变它的输出)。