dsh-jlink

August 14, 2026 · View on GitHub

版本 0.1 · 事实基准 本机实际安装的 @deepseek-ai/dsh@0.1.0-rc.6(Cordis 4.0.1) 状态 已实现(Phase 1+2 完成并通过 typecheck/build/单测;Phase 3 代码就位未硬件验证)→ 验收中。实现偏差见 README「实现偏差记录」 读者 执行编码任务的模型(如 Gemini Flash)。本文是规格书:第 9 章「硬约束清单」每一条都可被检查,实现时逐条自查;任何实现与第 2 章核心决策冲突即算错。


0. 怎么用这份文档

  1. 先读 第 2 章(核心决策)第 9 章(硬约束清单),再动手写代码。
  2. 编码前必须核对 附录 B 列出的基准文件(全部来自真实安装的 rc.6 包)。API 形状以那些文件为准,禁止凭记忆写 API
  3. 本文件夹自包含pnpm install && pnpm build 后即可挂进任意 DSH profile;整体移动到任何目录后重复这两步即可,无绝对路径依赖。

1. 目标与非目标

目标:把 jlink_mcp(Python MCP 服务器)的「管理器 + 工具 + 补丁注册」逻辑,重写为 DeepSeek Harness 原生 Cordis 插件(Host 面 + 浏览器面双端),并把可视化做成与工具同等地位的一等能力:内存 hexdump 视图、寄存器表格、RTT 终端、Flash 进度条、会话头状态灯——都是浏览器里的定制组件,而不是纯文本。

非目标

  • 不重写 Python 驱动逻辑(Phase 3 用薄 RPC 层复用 jlink_mcp 已验证的 pylink 代码)。
  • Phase 1/2 不做多厂商补丁(保留注册器,先实现 Flagchip)。
  • 不做 MCP 桥接(dsh-mcp-client)——本插件就是它的原生替代。

2. 核心决策(先定死)

#决策理由
D1服务之下加 DriverInterface 驱动抽象。Phase 3 实现 PythonDriver(stdio + ndjson JSON-RPC,复用 jlink_mcp 的 pylink 逻辑);开发期用 MockDriver,无硬件也能开发/演示全部 UI上层(工具/UI/补丁)与硬件后端解耦;将来可换 Node 直驱 DLL 而上层零改动
D2插件行挂 profile 根配置层(host plane),绝不放进 agent preset 的 isolate realm一把 J-Link 是进程级资源;工具全局可见、所有 agent 共享同一连接
D3数据通道分频:慢状态走 session projection(推送)、快数据走 Remote RPC 长轮询(RTT)、查询走 Remote RPC树外插件无法新增 ctx.remote.$on 事件(白名单是 app 侧固定数组,见 R2),projection+RPC 是唯一稳妥的树外通道
D4Remote RPC 用 SRC 模式 + 手写 src-json 贡献环境没有 dsh-typert-generator;SRC 是官方文档明确的 fallback 路径
D5补丁注册器与匹配算法从 Python 原样平移保证 FC7300 系列匹配行为与 jlink_mcp 完全一致(测试向量共用)
D6工具返回契约 = output.schema(canonical JSON,信封 {success,data,message,error})+ output.render(模型/人看的文本)继承 jlink_mcp 信封语义,同时获得 DSH「数据与渲染分离」的原生优势

3. 全新思路(设计哲学)

3.1 从「手搓」到「框架内建」

jlink_mcp 费心手搓的插件机制,DSH/Cordis 是框架级内建的。本方案不照抄 Python 结构,而是逐项映射:

jlink_mcp(Python 手搓)dsh-jlink(DSH 内建)获得的增益
__new__ 单例管理器Cordis Service + 依赖注入生命周期、realm 隔离、可注入、可测试
DevicePatchInterface(ABC)TS DevicePatch 接口编译期类型安全契约
DevicePatchManager 单例注册器Cordis 服务注册表 + 组合树运行时注册/注销、HMR、第三方可扩展
plugins/__init__.py 静态导出组合树 + Loader 发现按 profile 组合,无需改主程序
@mcp.tool()ctx.tools.register()一等工具:可 guard / restrict / 超时 / 并发策略 / agent scope
Pydantic 校验zod(Standard Schema)与全栈 schema 体系统一
{success,data,message,error} 信封output.schema + output.render模型吃 JSON、人看组件,两者分离
环境变量配置插件 Config + patch 层覆盖分层配置、可 --dump-config 检查
(没有 UI)toolview 键控槽 + slots全新能力:可视化

3.2 为什么原生 > MCP 桥接(最大化插件优势)

  1. 一等工具:原生注册的工具可被 DSH 的 guard/restrict/超时/并发策略/agent scope 治理;MCP 桥接工具只能拿到名字与 schema。
  2. 可视化tool.call.toolview 键控槽让每个工具结果渲染成专属组件——桥接工具永远只有文本。
  3. 组合性(插件套插件):补丁注册器让「ST 补丁」成为另一个插件在运行时挂载,无需改本项目。
  4. 深度联动:approval 策略、jobs、goal、subagent 都能通过 ctx.get('jlink') 感知硬件状态。
  5. HMR:改工具/服务热更新,不重启进程、不重连硬件。
  6. 生命周期:断线重连、进程退出清理由框架承担,而非 Python 单例硬扛。

3.3 可视化原则(贯穿全设计)

  • 每个工具一个专属视图(§7.2)。
  • 硬件状态常驻 UI(§7.1 状态灯)。
  • 慢状态推送、快数据流式(§4.2 通道分频)。
  • UI 只用宿主已声明的槽与 token,不碰布局全局(U1/U4)。

4. 总体架构

4.1 三平面

┌─ 浏览器(Client plane)────────────────────────────────────────┐
│  Header JLink 状态灯 │ RTT 控制台 │ Flash 进度 │ hexdump/寄存器视图 │
│  ctx.slots.register      ctx.remote.jlink.rttRead(since, signal) │
│                          projections.faceOf('jlink')(Phase 3)  │
└──────────────────────────┬──────────────────────────────────────┘
                           │ HTTP /api(Typert RPC)+ session/projection 帧
┌─ Host(Node 进程)─────────────────────────────────────────────┐
│  JLinkService(连接状态机) ──► PatchRegistry ──► FlagchipPatch… │
│     │                                                          │
│     │  DriverInterface:MockDriver / PythonDriver / GdbDriver   │
│  工具注册(ctx.tools.register) │ Remote RPC(SRC) │ projection │
└────────────────────────────────────────────────────────────────┘
                           │ ndjson JSON-RPC(stdio,Phase 3)
                     ┌─────┴──────────┐
                     │ python/driver.py│── pylink ──► J-Link 硬件
                     └────────────────┘

4.2 数据通道总表

通道方向用途本插件用法
工具调用模型 → Host调试操作§6.5 工具清单(23 个)
Remote RPCUI → Host查询 / 长轮询jlink/statusjlink/rttRead
session/projection 帧Host → UI小快照推送jlink 键:连接/芯片/flash 快照(Phase 3)
toolview 槽工具结果 → UI结果可视化键控注册(§7.2)
(禁用)remote 事件Host → UI——树外不可扩展,见 R2

5. 包结构(自包含)

dsh-jlink-plugin/
├── DESIGN.md                  # 本文件
├── README.md                  # 用户文档(安装/配置/工具目录/常见错误)
├── package.json               # 硬形状见 §8.1
├── tsconfig.json              # strict、ESM、erasable 语法(禁 enum/namespace)
├── tsdown.config.ts           # 双面产物:host + client + remote
├── python/
│   └── README.md              # Phase 3 才实现 driver.py(ndjson RPC 协议定义)
├── src/                       # ── Host 面 ──
│   ├── index.ts               # 入口 apply(ctx, config)
│   ├── config.ts              # zod Config
│   ├── types.ts               # 全部共享类型 + 事件声明合并
│   ├── errors.ts              # 错误码(平移 exceptions.py)
│   ├── utils.ts               # 工具函数(平移 utils.py)
│   ├── driver/
│   │   ├── interface.ts       # DriverInterface(D1)
│   │   └── mock.ts            # MockDriver(Phase 1/2)
│   ├── service.ts             # JLinkService 状态机(← jlink_manager.py)
│   ├── remote-spec.ts         # 手写 TYPERT 贡献数据(host/client 共用,D4)
│   ├── projection.ts          # session projection 单元(Phase 3)
│   ├── patch/
│   │   ├── interface.ts       # DevicePatch(← device_patch_interface.py)
│   │   ├── registry.ts        # PatchRegistry(← device_patch_manager.py)
│   │   └── flagchip.ts        # FlagchipPatch(← plugins/flagchip_patch.py)
│   └── tools/
│       ├── connection.ts      # 5 工具(← tools/connection.py)
│       ├── device.ts          # 4 工具(← tools/device_info.py)
│       ├── memory.ts          # 4 工具(← tools/memory.py)
│       ├── debug.ts           # 7 工具(← tools/debug.py)
│       └── flash.ts           # 3 工具(← tools/flash.py)
├── src/client/                # ── 浏览器面 ──
│   ├── index.ts               # client 入口 apply(dsh.client 标记指向这里)
│   ├── remote.ts              # ctx.remote.$mount(手写贡献)
│   ├── header-control.tsx     # 会话头状态灯 + 弹窗(§7.1)
│   └── toolviews/
│       ├── memory.tsx         # hexdump 视图
│       ├── registers.tsx      # 寄存器表格
│       ├── chip.tsx           # 芯片信息卡
│       ├── rtt.tsx            # RTT 终端(Phase 3)
│       └── flash.tsx          # 烧录进度(Phase 3)
└── tests/
    ├── patch-match.test.ts    # 匹配算法测试向量(与 jlink_mcp 一致)
    ├── envelope.test.ts       # 信封契约测试
    └── service.test.ts        # 状态机测试(MockDriver)

6. Host 设计

6.1 Config(config.ts)

import { z } from 'zod'

export const Config = z.object({
  driver: z.enum(['mock', 'python', 'gdb']).default('mock'),
  pythonCommand: z.string().default('python'),      // Phase 3
  defaultInterface: z.enum(['SWD', 'JTAG']).default('JTAG'),
  defaultTimeoutMs: z.number().int().positive().default(10000),
  maxMemoryReadSize: z.number().int().positive().default(65536),
  patchDir: z.string().optional(),                  // 外部 JLinkDevices.xml 目录
  svdDir: z.string().optional(),                    // Phase 4
  autoReconnect: z.boolean().default(false),
})
export type JlinkConfig = z.infer<typeof Config>

约束:schema 只增不改;形状变化必须同步 bump 相关 stateVersion 与文档。

6.2 DriverInterface(driver/interface.ts)

export interface DriverInterface {
  /** 生命周期 */
  connect(opts: { interface?: 'SWD'|'JTAG'; chip?: string; serial?: string }): Promise<Envelope<DeviceInfo>>
  disconnect(): Promise<Envelope<null>>
  /** 调试控制 */
  halt(): Promise<Envelope<CpuState>>
  run(): Promise<Envelope<CpuState>>
  step(): Promise<Envelope<CpuState>>
  reset(): Promise<Envelope<null>>
  getCpuState(): Promise<Envelope<CpuState>>
  /** 内存/寄存器 */
  readMemory(address: number, length: number): Promise<Envelope<Uint8Array>>
  writeMemory(address: number, data: Uint8Array): Promise<Envelope<null>>
  readRegisters(names?: string[]): Promise<Envelope<Record<string, number>>>
  writeRegister(name: string, value: number): Promise<Envelope<null>>
  /** Flash(Phase 2+) */
  eraseFlash(start: number, end: number): Promise<Envelope<null>>
  programFlash(address: number, data: Uint8Array, verify: boolean): Promise<Envelope<null>>
  verifyFlash(address: number, data: Uint8Array): Promise<Envelope<null>>
}

语义约定:

  • 所有方法返回信封,不 throw 业务错误;编程错误(参数非法等)可 throw。
  • 实现层负责请求互斥(同驱动实例内串行);上层不假设并发。
  • MockDriver 维护一张内存 Map 模拟 Flash/RAM/寄存器,行为与真实驱动一致(未 halt 读内存返回 JLINK_NOT_HALTED 错误码)。

6.3 JLinkService 状态机(service.ts)

export class JLinkService extends Service {
  constructor(ctx: Context, config: JlinkConfig) {
    super(ctx, 'jlink')          // 注册为 ctx['jlink'],随 fiber 自动销毁
    this.driver = createDriver(config.driver, config)
  }
  // 状态机:disconnected → connecting → connected(halted|running) → error
  private state: ConnectionState = { status: 'disconnected' }
  private setState(next: ConnectionState) { /* 单点修改 + 记录 lastChangedAt */ }
  // 业务方法:connect/disconnect/halt/run/reset/step/readMemory/...(供工具与 Remote 共用)
}

约束:

  • 工具与 Remote 不得直接碰 driver,必须经 JLinkService 业务方法(H 约束)。
  • 断线重连语义平移自 dsh-mcp-client 的 reconnect 配置:初始 500ms、指数退避、上限 30s、最多 10 次。
  • 进程内只允许一个 JLinkService 实例(由 apply 提供;禁止 new JLinkService)。

6.4 Patch 系统(patch/)

// patch/interface.ts —— 平移 device_patch_interface.py
export interface DevicePatch {
  readonly vendorName: string
  readonly patchVersion: string
  isAvailable(): boolean
  readonly devices: DeviceInfo[]
  readonly deviceNames: string[]
  matchDeviceName(partial: string): string | null
  findSimilarDevices(partial: string, limit?: number): string[]
  getDeviceNameSuggestions(partial: string): string
}

// patch/registry.ts —— 平移 device_patch_manager.py(含全部去重/优先级逻辑)
export class PatchRegistry extends Service {
  constructor(ctx: Context) { super(ctx, 'jlink.patches') }
  register(patch: DevicePatch): void       // vendor 去重,重复告警
  unregister(vendor: string): boolean
  matchDeviceName(chip: string): [string, DevicePatch] | null
  // ...
}

匹配算法必须逐行平移(测试向量共用,见 Phase 2 DoD):

  1. 精确匹配(忽略大小写,O(1) 预计算小写字典)
  2. 前缀匹配 → 3. 包含匹配 → 4. 占位符模糊匹配
  3. 多命中时排除 Unlock/Factory/FromRom/Core/_64/ETM 关键词
  4. 批次优先级 T1C > T1B > T1A

FlagchipPatch 解析 JLinkDevices.xml(内置一份于包内 resources/,或经 patchDir 外置——与 jlink_mcp 的资源解析优先级一致)。

6.5 工具清单

命名与 jlink_mcp 完全一致。阶段与硬指标:

工具阶段关键参数timeoutMs前置条件isConcurrencySafe
list_jlink_devicesP1——10000——Phase 3 评估
get_connection_statusP1——10000——Phase 3 评估
halt_cpuP1——5000已连接
connect_deviceP2serial?, interface?, chip_name?15000——
disconnect_deviceP2——10000——
match_chip_nameP2chip_name5000——
get_target_infoP2——10000已连接
get_target_voltageP2——10000已连接
scan_target_devicesP2——15000——
list_device_patchesP2——5000——
read_memoryP2address, length(≤65536)15000已 halt
write_memoryP2address, data15000已 halt
read_registersP2names?10000已 halt
write_registerP2name, value10000已 halt
reset_targetP2——10000已连接
run_cpuP2——5000已连接
step_instructionP2——5000已连接
get_cpu_stateP2——5000已连接
set_breakpointP2address, type?5000已连接
clear_breakpointP2address5000已连接
erase_flashP2start_address, end_address300000已连接
program_flashP2address, data, verify=true300000已连接
verify_flashP2address, data120000已连接
rtt_start / rtt_stop / rtt_read / rtt_write / rtt_get_statusP3——10000已连接
start_gdb_server / stop_gdb_server / get_gdb_server_statusP3port?, device?10000——
SVD 5 工具P4————————

前置条件语义(继承 jlink_mcp 最佳实践):memory/register 读写前必须 halted;工具内检查,未 halted 返回结构化错误(error.code='JLINK_NOT_HALTED',message 提示先调 halt_cpu)。

6.6 工具注册模板(tools/memory.ts)

ctx.tools.register({
  name: 'read_memory',
  description: '读取目标内存。读取前 CPU 必须已 halt(未 halt 返回 JLINK_NOT_HALTED)。',
  parameters: {
    type: 'object',
    properties: {
      address: { type: 'integer', minimum: 0 },
      length: { type: 'integer', minimum: 1, maximum: 65536 },
    },
    required: ['address', 'length'],
  },
  output: {
    schema: {
      type: 'object',
      properties: {
        success: { type: 'boolean' },
        data: { type: 'object', properties: { address: { type: 'integer' }, bytes: { type: 'string' }, hex: { type: 'string' } } },
        message: { type: 'string' },
        error: { type: ['object', 'null'] },
      },
      required: ['success', 'message'],
    },
    render(_args, value) {
      return value.success
        ? [{ type: 'text', text: value.data.hex }]        // 模型看到 hexdump 文本
        : [{ type: 'text', text: 'error: ' + value.error?.message }]
    },
  },
  timeoutMs: 15000,
  async execute(args, exec) {
    return this.ctx.get('jlink').readMemory(args.address, args.length)   // 返回 canonical JSON
  },
})

6.7 Remote RPC(remote-spec.ts,D4)

端点(最小且稳定):jlink/status(Phase 1)、jlink/rttRead(since, signal)(Phase 3)。

SRC 硬约束(来自 dsh-typert-protocol):

  • 方法只允许简单位置参数(string/number/boolean/普通对象,全部 JSON-safe);
  • 禁止解构参数、默认值、rest 参数、重载;
  • 可选末位 signal: AbortSignal(取消感知;SRC 识别保留名)。

手写贡献(host/client 共用同一份数据,codec 一律 mode:'src-json'):

// remote-spec.ts —— 手写 TYPERT 贡献(D4)。字段形状以附录 B 基准文件为准!
export const JlinkRemoteSpec = {
  package: '@can/dsh-jlink',
  face: 'host',
  schemas: [],
  invocations: [
    {
      id: '@can/dsh-jlink#jlink/status',
      service: 'jlink',
      namespace: 'jlink',
      method: 'status',
      invocation: { kind: 'direct' },       // 无 scope、无 lookup(进程级服务)
      parameters: [],                        // 全部 source:'json';此处无参
      result: { mode: 'src-json', typeSymbol: '@can/dsh-jlink/types#JlinkStatusView' },
      sourceLocation: { file: 'src/remote-spec.ts', line: 0, column: 0 },
    },
    // rttRead: parameters 含 { name:'since', wire:'since', source:'json', codec:{mode:'src-json',...} }
    // 取消感知的 descriptor 字段以 InvocationDescriptor 类型声明为准(typert-protocol)
  ],
}

Host 侧(index.ts 内):ctx.typert.register(JlinkRemoteSpec)(typert-loader 的 packages 备选,但直接 register 最简)。 Client 侧(client/remote.ts):await ctx.remote.$mount(JlinkRemoteSpec) 后使用 ctx.remote.jlink.status()

⚠️ 实现前必须先读dsh-goal/lib/typert.host.jsdsh-goal/lib/typert.remote-client.js(TYPERT/TYPERT_REMOTE 数据基准)、dsh-typert-protocol/lib/types/types.d.tsInvocationDescriptor 等字段真名)。禁止臆造字段名;拿不准时宁可少一个可选字段也不要编造。

6.8 Projection(projection.ts,Phase 3)

ctx.sessionProjections.register({
  key: 'jlink',
  schema: z.object({
    connected: z.boolean(),
    chip: z.string().nullable(),
    voltage: z.number().nullable(),
    cpuState: z.enum(['halted', 'running', 'unknown']).nullable(),
    flash: z.object({ phase: z.enum(['idle', 'erasing', 'programming', 'verifying']), percent: z.number() }).nullable(),
  }),
  init: () => ({ connected: false, chip: null, voltage: null, cpuState: null, flash: null }),
  apply(state, event) { return state },  // 不关心的事件必须返回同引用(same-reference gate)
  view: (s) => s,
  stateVersion: 1,
})

约束:值必须小(<2KB);init/apply/view 全同步;形状变化 bump stateVersion;状态变化以 whole-value 事件提交(经 SessionEventMap 声明合并 'jlink/state'),由 projection 折叠;禁止把 RTT 高频数据放进 projection。


7. Client 设计(src/client/)

7.1 会话头状态灯(header-control.tsx)

  • 槽:conversation.session.header.actions(dsh-client-ui-jobs 已证明该槽存在且持续渲染;注册一个条目即可)。
  • 行为:2s 间隔轮询 ctx.remote.jlink.status();四态灰/绿/黄/红 = 未连接/已连接/运行中/错误;点击弹窗:连接参数(接口/芯片/序列号)、halt/run/reset 快捷按钮(按钮调 Remote 方法,不直接发工具调用)。
  • Phase 3 后改吃 jlink projection 推送,轮询降级为兜底。
  • 轮询 interval 与 RPC 订阅随 fiber 卸载清理(U3)。

7.2 工具结果视图(tool.call.toolview 键控注册)

注册模式(来自 dsh-client-ui-tool 的键控链式槽):

ctx.slots.inject('tool.call.toolview', () =>
  ctx.slots.register({ name: 'tool.call.toolview', key: 'read_memory' }, MemoryToolView))

视图清单(组件只读工具结果负载,发 RPC):

key(工具名)组件渲染内容
read_memorymemory.tsx地址列 + hexdump + ASCII 列
write_memorymemory.tsx写入确认(地址/长度)
read_registersregisters.tsx寄存器表:名/十六进制/十进制
get_target_infochip.tsx芯片卡片(core/Flash/RAM/电压)
get_connection_statuschip.tsx连接状态卡
erase_flash / program_flash / verify_flashflash.tsx阶段 + 进度条(Phase 3 接 projection 实时进度)
rtt_readrtt.tsx终端样式日志(Phase 3)

7.3 RTT 控制台(Phase 3)

  • 挂载:状态灯弹窗内 tab,或独立面板(实现时先枚举宿主已声明槽再定,见 U1)。
  • 数据:ctx.remote.jlink.rttRead(since) 长轮询(递增游标 + AbortSignal 取消);环形缓冲 ≤1000 行;自动滚动;可暂停。

7.4 Flash 进度(Phase 3)

  • 进度数据 = jlink projection 的 flash 子对象(阶段 + 百分比);烧录工具 timeoutMs 已放宽至 300s,模型侧不会被超时打断。

7.5 组件硬约束

React 18;样式只用 ui-primitives token;文案走自有 locale 命名空间 jlink;组件为纯函数优先、无窗口依赖;订阅全生命周期清理。


8. 构建与安装

8.1 package.json 硬形状(B 约束)

{
  "name": "@can/dsh-jlink",
  "version": "0.1.0",
  "type": "module",
  "main": "lib/index.js",
  "types": "lib/types/index.d.ts",
  "exports": {
    ".": { "types": "./lib/types/index.d.ts", "default": "./lib/index.js" },
    "./types": { "types": "./lib/types/types.d.ts", "default": "./lib/types/types.js" },
    "./client": { "types": "./lib/types/client/index.d.ts", "default": "./lib/client.js" },
    "./remote": { "types": "./lib/types/remote.d.ts", "default": "./lib/remote.js" },
    "./package.json": "./package.json"
  },
  "dsh": {
    "client": {
      "inject": [
        "@deepseek-ai/dsh-client-runtime",
        "@deepseek-ai/dsh-client-ui-primitives",
        "@deepseek-ai/dsh-client-ui-conversation",
        "@deepseek-ai/dsh-client-ui-slots"
      ],
      "platform": "web"
    }
  },
  "files": ["lib/", "python/", "resources/"],
  "peerDependencies": {
    "@deepseek-ai/cordis": "^4.0.1",
    "@deepseek-ai/dsh-tools": "^0.1.0-rc.6",
    "@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
    "@deepseek-ai/dsh-typert-protocol": "^0.1.0-rc.6",
    "@deepseek-ai/dsh-api-gateway": "^0.1.0-rc.6",
    "@deepseek-ai/dsh-session-projection": "^0.1.0-rc.6",
    "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6",
    "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6",
    "@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.6",
    "@deepseek-ai/dsh-client-ui-conversation": "^0.1.0-rc.6"
  },
  "dependencies": { "zod": "^4.4.3" },
  "scripts": {
    "build": "tsdown",
    "watch": "tsdown --watch",
    "typecheck": "tsc --noEmit",
    "test": "vitest run"
  }
}

注意:dsh.client.inject 只列实际 import 了其公开 API 的包;peerDependencies 与实际 import 一一对应(B4)。dsh.client 标记是 Loader 把本包 client 面打进浏览器 bundle 的依据。

8.2 tsdown 产物

  • lib/index.js(host 入口)→ exports["."]
  • lib/client.js(client 入口)→ exports["./client"]
  • lib/remote.js(TYPERT_REMOTE 数据)→ exports["./remote"]
  • types 全部随产物输出;ESM only。

8.3 装载步骤(写入 README)

# 1) 创建 profile(若尚无;具体命令以 CLI 行为参考为准)
dsh plugin --profile can add <本插件路径或 npm>

# 2) profile 的 cordis.patch.yml 增加(host plane 根层,D2):
#    - id: jlink
#      name: '@can/dsh-jlink'
#      config: { driver: mock }

# 3) 启动并验证
dsh web
dsh --profile web --dump-config      # 应能看到 jlink 插件行与配置

8.4 HMR 工作流

  • Host:编辑插件入口 → cordis-plugin-hmr 重载(不重启进程、不重连硬件)。
  • Client:pnpm watch 重建 lib/client.js → 刷新浏览器。不要假设 checkout 的 dev:web watcher 会重建树外插件——树外插件的 client bundle 由本包自建自刷。

9. 硬约束清单(实现者逐条自查)

9.1 通用(C)

  • C1 全部源码 ESM + 严格 TS;禁 enum/namespace;公开函数全类型注解;docstring 中英双语(沿用 jlink_mcp 惯例)。
  • C2 禁模块级可变全局;一切状态在 Service 实例内。
  • C3 禁 import DSH 包的 ./src/* 深层路径,只用公开 exports。
  • C4 禁硬编码绝对路径;包内资源用 import.meta.url 相对解析。
  • C5 工具名与 jlink_mcp 完全一致;工具/服务/端点名满足 [A-Za-z0-9_-]、≤64 字符。
  • C6 包名占位 @can/dsh-jlink;改名需同步 3 处(package.json name、cordis.yml name、remote-spec 的 package 字段)。

9.2 Host(H)

  • H1 工具必须声明 output.schema + output.renderexecute 返回值必须通过该 schema。
  • H2 业务失败不 throw:返回 {success:false, message, error:{code,message}};仅编程错误可 throw。
  • H3 execute 的异步工作必须观察 exec.signal
  • H4 Phase 1/2 不声明 isConcurrencySafe(全串行);Phase 3 只对纯读且 driver 串行安全者开 true。
  • H5 memory/register 读写前必须 halted(工具内检查,未 halted 返回 JLINK_NOT_HALTED)。
  • H6 JLinkService 单实例:由 apply 提供,禁 new JLinkService
  • H7 Remote 方法仅简单参数 + 可选末位 signal;禁解构/默认值/rest/重载。
  • H8 手写贡献逐字段对齐附录 B 基准;codec mode:'src-json';改端点同步 host/client 两份数据并 bump 版本。
  • H9 Projection 单元全同步、same-reference、值 <2KB、bump stateVersion(Phase 3)。
  • H10 补丁加载失败只 warning 不致命(平移 jlink_mcp 容错语义)。
  • H11 插件行必须在 cordis.patch.yml 声明 inject: ['tools', 'typert'](loader 的 Proxy 守卫:未声明即访问 ctx.tools 会抛 "cannot get property without inject";模块导出 inject 仅对 client 面生效)。

9.3 Client(U)

  • U1 只注册已声明槽:conversation.session.header.actionstool.call.toolview;用其它槽前先确认宿主 SlotMap 声明;未声明的目标用 ctx.slots.inject 等待模式;禁凭空发明槽名。
  • U2 toolview 注册必须键控(key: '<工具名>')。
  • U3 所有 interval / RPC / $on 订阅随 fiber 或组件卸载清理。
  • U4 React 18;样式只用 ui-primitives token;文案走 jlink locale 命名空间。
  • U5 组件不发工具调用;需要动作调 Remote 方法。
  • U6 修正(原假设错误):remote.jlink 不得出现在客户端 inject 中。该命名空间服务由本 entry 在 apply 内 await ctx.remote.$mount(contribution) 动态创建(api-gateway 按贡献注册 remote.<ns> 服务),inject 它会导致 loader 在 apply 前解析依赖时服务尚不存在 → entry 永远 pending → web boot 失败。挂载者只 inject ['remote'](同 dsh-api-remotes 官方模式);挂载完成后 ctx.remote.jlink 经 Service 代理按 reflect.props['remote.jlink'] 转发,无需声明(与 ui-goal 依赖宿主 dsh-api-remotes 提供的 remote.goals 不同:本插件自包含,必须先自挂载再消费)。

9.4 构建/打包(B)

  • B1 exports../client./types./remote./package.json
  • B2 dsh.client 标记必须存在且 inject 列表真实。
  • B3 pnpm buildlib/index.jslib/client.jslib/remote.js 齐全。
  • B4 peerDependencies 与实际 import 一一对应(^0.1.0-rc.6)。
  • B5 python/ 目录 Phase 1/2 只放 README;Phase 3 才实现 driver.py 与 ndjson 协议。

9.5 禁止事项(F,红线)

  • F1 禁用 dsh-mcp-client 桥接 jlink_mcp 作为交付物。
  • F2 禁绕过 DriverInterface 直接操作 pylink / J-Link DLL。
  • F3 禁把 RTT 高频数据写进 projection 或 session 事件日志。
  • F4 禁 UI 直接读工具注册表拿数据(UI 数据走 Remote/projection)。
  • F5 禁把插件行放进 agent preset / isolate realm(D2)。
  • F6 禁修改 DSH checkout 内任何文件(本包完全自包含)。
  • F7 program_flash 默认 verify=true,禁跳校验。

10. 分阶段实施(每阶段以 DoD 验收)

Phase 0(已完成)本设计文档。

Phase 1 — 骨架跑通(无硬件)

范围:package.json/tsconfig/tsdown;MockDriver;JLinkService 骨架;3 工具(list_jlink_devices / get_connection_status / halt_cpu);jlink/status Remote;会话头状态灯;装载验证。 完成定义(全部满足才算完成):

  • pnpm install && pnpm build 产物齐全(B3)
  • --dump-config 可见 jlink 插件行
  • 浏览器会话头出现状态灯(mock:灰=disconnected;弹窗可用)
  • 模型可调用 3 个工具并返回合法 envelope
  • host 插件改一行 → 无重启热更新生效

Phase 2 — 全量工具 + 补丁 + 首批视图

范围:23 工具全量;PatchRegistry + FlagchipPatch(XML 解析);toolview:hexdump、寄存器表、芯片卡、连接卡。 DoD:

  • match_chip_name 测试向量与 jlink_mcp 一致:FC7300F4MDD → FC7300F4MDDxXxxxT1C;FC7300F4MDDS → FC7300F4MDSxXxxxT1C;多命中时 T1C>T1B>T1A
  • 浏览器工具结果渲染为组件视图(非纯文本卡片)
  • 23 工具在 MockDriver 下含错误分支全部行为合理

Phase 3 — 真硬件 + RTT + 推送

范围:PythonDriver(ndjson RPC 协议 + driver.py 复用 jlink_mcp 逻辑);rtt/gdb 工具;RTT 控制台;projection 推送 + flash 实时进度。 DoD: 真板 connect/halt/read_memory 成功;RTT 终端实时出数据;烧录进度条推进;断线重连成功一次。

Phase 4 — 可选增强

Node 直驱 DLL、SVD 工具、多厂商补丁示例、语义检索。


11. 验收清单(最终交付)

  • 文件夹整体搬移到新路径后 install+build+装载全通过(自包含)
  • 第 9 章每条约束自查通过
  • README 含安装/配置/工具目录/常见错误
  • tests/ 含 mock 单测(patch 匹配、envelope 契约、状态机)
  • 无 console.error 泄漏;UI 卸载无订阅泄漏

12. 风险与开放问题

  • R1 typert-generator 不在环境 → D4:手写 src-json 贡献(已有真实基准文件可对齐)。
  • R2 remote 事件不可扩展ctx.remote.$on 的合法键来自 app 侧固定白名单(dsh-api-remotesAPI_REMOTE_FORWARDED_EVENTS),树外插件无法新增 → 因此 D3 用 projection + RPC,不用 remote events。若将来需要真推送事件,须改 web app 装配,不属于本包范围。
  • R3 pylink 仅 Python → D1 PythonDriver 覆盖。
  • R4 Flash 长耗时 → timeoutMs 300s + 进度投影。
  • R5 rc.6 API 演进 → 附录 B 基准文件路径化,升级时重核对。

jlink_mcp 文件dsh-jlink 文件说明
server.pysrc/index.ts + tools/*工具注册(@mcp.tool → ctx.tools.register)
jlink_manager.pysrc/service.ts单例 → Service + 状态机
config_manager.pysrc/config.tsPydantic → zod
svd_manager.py(Phase 4)src/svd.ts保留 pickle 缓存思路
device_patch_interface.pysrc/patch/interface.tsABC → TS interface
device_patch_manager.pysrc/patch/registry.ts单例注册器 → Service
plugins/flagchip_patch.pysrc/patch/flagchip.tsXML 解析 + 预计算小写字典 + lru_cache → Map
models/*.pysrc/types.tsPydantic → zod schema
exceptions.pysrc/errors.ts错误码枚举(JLINK_NOT_HALTED 等)
utils.pysrc/utils.tslogger/hexdump 等
(新增)src/driver/*jlink_mcp 没有的驱动抽象层(D1)
(新增)src/client/*可视化面(本设计最大增量)
(新增)src/remote-spec.tsRemote RPC 手写贡献(D4)

附录 B:已验证 API 速查(rc.6 基准,编码前必读)

本机基准路径(npx 缓存):C:\Users\qxw0112\AppData\Local\npm-cache\_npx\1e7f6d9597241db0\node_modules\@deepseek-ai\。换机器后以 profile 的 node_modules 内同版本包 为准。

主题基准文件 / 包要点
工具注册dsh-tools/lib/types/index.d.tsregister(def);ToolSchema={name,description,parameters(JSON Schema)};output={schema,render,presentationMeta?};execute(args,exec);timeoutMs?;isConcurrencySafe?
插件/服务cordis/src/service.ts、registry.tsService 基类 super(ctx,name);Plugin=Function/Constructor/Object(apply)
Remote 声明dsh-typert-protocol/lib/types/*@Remote/@RemoteScope/TypertRemoteService/bindTypertRemote;SRC 限制;InvocationDescriptor 字段
TYPERT 基准dsh-goal/lib/typert.host.jsexport const TYPERT = {package,face,schemas,invocations}
TYPERT_REMOTE 基准dsh-goal/lib/typert.remote-client.jsclient 挂载贡献数据形状
事件选择集dsh-typert-protocol/lib/types/types.d.tsTypertRemoteEventSelection(声明合并);注意 R2:运行时白名单在 app 侧
Projectiondsh-session-projection/README.mdctx.sessionProjections.register({key,schema,init,apply,view,stateVersion})
Client 挂载/订阅dsh-api-gateway/lib/client.js$mount(contribution)/$on(event)
dsh.client 标记dsh-client-ui-jobs/package.json"dsh":{"client":{"inject":[...],"platform":"web"}} + exports["./client"]
toolview 槽dsh-client-ui-tool/README.md键控 tool.call.toolviewctx.slots.inject(...) + register({name,key},Component)
会话头槽dsh-client-ui-jobs/README.mdconversation.session.header.actions
slot 系统dsh-client-ui-slots/README.mdregister({name,children?,store?,inject?,key?},Component);SlotMap 声明合并
client runtimedsh-client-runtime/README.mdprojections 客户端读取(faceOf/useProjection)

附录 C:术语

host plane(主机面,Node)/ client plane(浏览器面)/ 槽 slot(UI 插槽)/ 键控槽(按 key 路由的链式槽)/ realm(服务作用域)/ projection(会话投影:事件折叠出的可推送小快照)/ SRC(无生成描述符的 Remote fallback)/ TYPERT(Typert RPC 生成产物与协议)/ envelope({success,data,message,error} 信封)