Repository Wiki
deepseek-ai/deepseek-harness

浏览器应用启动与客户端插件装配

浏览器入口创建 AppWebEntry,在注入完成后建立模块系统与 Cordis 上下文,预取首批插件,再交由 bootClient 装配客户端入口并调用 mountClient 挂载界面。入口 · 启动内核

Purpose and Scope

本文聚焦 apps/web 浏览器入口到 AppWebEntry 的启动顺序、桌面载体注入就绪门、模块装载配置、插件预取、失败展示及释放。插件内部如何实现 bootClient、具体渲染器如何实现 mountClient、服务端如何生成注入表,以及桌面宿主的完整协议均属于相邻主题;本文只讨论这些边界在启动链上的调用关系。由于本页没有读取这些函数的实现,不推断它们内部的插件排序或渲染细节。boot.ts

Overview

apps/web 是基于 @deepseek-ai/dsh-client-web shell 的 Vite 应用入口;包脚本提供 dev、build、watch 和独立的 preview 构建/服务命令。package.json · package.json。普通网页直接调用 entry.run();检测到 dshDesktopBoot 时,入口先异步获取注入数据和流地址,加载脚本后放行同一条启动流程。main.ts

Architecture

Loading diagram...

Source: main.ts, boot.ts, boot.ts

AppWebEntry 保留容器、可选模块传输替身、启动页,以及运行中产生的 Context、模块系统与 manifest;构造时立即创建 BootPage,但直到 run() 才加载插件和挂载 UI。boot.ts

启动路径与装配次序

浏览器入口和桌面载体

入口先检查 #root 是否存在,缺失时抛出 web app: missing #root;随后构造 AppWebEntry。桌面模式必须存在 __DSH_BOOT_READY__ deferred,否则抛出明确的就绪门错误。desktop.ready() 返回注入表与 streamBaseUrl;入口设置 __DSH_TRANSPORT__ = { ownsHost: true, streamBaseUrl },通过向 document.head 追加 script 元素逐个加载 applyIndexInjections 请求的脚本,成功后 gate.resolve(),失败时 gate.reject(error)。entry.run(...) 不等待这段注入逻辑完成,而由 run() 自己等待门的 promise,避免读取尚未注入的启动全局量。main.ts · boot.ts

typescript
const el = document.getElementById('root') if (el === null) throw new Error('web app: missing #root') const entry = new AppWebEntry(el)

Source: main.ts

typescript
1await applyIndexInjections(injections, src => new Promise<void>((resolve, reject) => { 2 const script = document.createElement('script') 3 script.src = src 4 script.onload = () => { resolve() } 5 script.onerror = () => { reject(new Error(`desktop web: failed to load ${src}`)) } 6 document.head.append(script) 7})) 8gate.resolve()

Source: main.ts

装载器、预取与渲染交接

run() 等待可选的就绪门;不存在门时不等待。随后检查 window.__ModuleLoader__,以 __DSH_BOOT__、getStaticModules()、可选传输 loadBundle 和构造函数传入的 seams 创建模块系统;展开顺序使显式 seams 覆盖预注入的传输钩子。读取 modules.manifest 后,先发起立即层预取,再创建 Cordis Context、设置启动页插件总数,等待预取,最后调用 bootClient。启动回调按名称写入状态;有宿主的失败处理器时,失败状态不会写到启动页。插件启动结束后安装窗口拖拽区域监测,再以同一 ctx 与容器调用 mountClient。boot.ts

typescript
1this.modules = moduleLoader.create({ 2 boot: win.__DSH_BOOT__, 3 staticModules: getStaticModules(), 4 ...transport?.loadBundle === undefined ? {} : { loadBundle: transport.loadBundle }, 5 ...this.seams, 6}) 7this.manifest = this.modules.manifest

Source: boot.ts

typescript
1const prefetching = this.prefetchImmediateTier() 2const ctx = new Context() 3this.ctx = ctx 4this.page.setTotal(this.manifest.plugins.length) 5await prefetching 6await bootClient({ 7 ctx, 8 modules: this.modules, 9 manifest: this.manifest, 10 onEntryState: (name, state) => { 11 if (onFailure === undefined || state !== 'failed') this.page.setState(name, state) 12 }, 13})

Source: boot.ts

Core Flow

Loading diagram...

Source: main.ts, boot.ts

预取仅处理 manifest.plugins 中 immediately 为真的行,并对这些行并行调用 modules.prefetch(row.id);预取错误在该阶段被吞掉,源码注释说明正式 Loader import 将重试并报告 bundle 失败。这里不应将预取完成理解为插件已经激活。boot.ts

Usage Examples

下例是入口的实际启动调用:桌面模式传递失败报告函数,普通网页模式不传。函数内部捕获初始化的同步异常,桌面宿主存在时通过 failed() 异步报告;无宿主则重新抛出原始原因。main.ts · main.ts

typescript
1void entry.run(desktop === undefined ? undefined : reportFailure) 2} catch (reason) { 3 reportFailure(reason) 4}

Source: main.ts

下面是 AppWebEntry 内部的高级用法:插件装配之后,先启用拖拽区域监测再移交渲染器;销毁时逆向停止监测、释放 Cordis fiber,并释放启动页。调用方若管理入口生命周期,可调用公开的 dispose()。boot.ts

typescript
this.stopDragRecall = installWindowDragRecall({ document: this.container.ownerDocument }) await mountClient(ctx, this.container)

Source: boot.ts

typescript
1async dispose(): Promise<void> { 2 this.stopDragRecall?.() 3 this.stopDragRecall = undefined 4 const ctx = this.ctx 5 this.ctx = undefined 6 if (ctx !== undefined) await ctx.fiber.dispose() 7 this.page.dispose() 8}

Source: boot.ts

配置与启动输入

这里的运行时输入来自全局量、构造参数和 manifest,而非该入口中的环境变量配置;以下列出源码中可核实的含义。main.ts · boot.ts

输入类型/缺省用途
#rootDOM 元素;必需AppWebEntry 的容器;不存在即抛错。
dshDesktopBoot可选,缺省 undefined启用桌面注入流程,提供 ready() 和 failed(message)。
__DSH_BOOT_READY__可选全局 deferred;桌面模式必需run() 等待其 promise;桌面注入完成后 resolve。
window.__ModuleLoader__必需的模块装载 facade创建模块系统;缺失时 run() 进入失败处理。
__DSH_BOOT__注入的启动数据透传给 moduleLoader.create;此处未定义其内部字段。
__DSH_TRANSPORT__.loadBundle可选用于自带 bundle 字节的传输;显式 seams.loadBundle 可覆盖。
seams可选 BootSeams构造时提供的模块传输替身,源码注明供 jsdom 测试替换。
manifest.plugins[].immediately来自模块系统 manifest标记要提前并行预取的插件条目。

上述行分别对应入口全局与 DOM 检查、模块系统创建和预取筛选。

API Reference

API参数返回/语义可观察的失败
new AppWebEntry(container: HTMLElement, seams?: BootSeams)container:挂载 DOM;seams:可选 loadBundle 替身构造启动页,尚不开始加载。构造函数中未声明显式异常。
run(onFailure?: (reason: unknown) => void): Promise<void>可选的宿主失败报告回调等待注入、装配插件并挂载;捕获内部错误后显示启动页错误或调用回调,因此通常通过 resolve 完成失败报告。catch 内调用 console.error;若所提供回调自身抛错,源码未再捕获。
dispose(): Promise<void>无停止 drag recall,等待 ctx.fiber.dispose(),释放 BootPage。未在此方法内设置异常捕获。

签名及执行路径见 boot.ts 与 boot.ts。BootSeams 只抽取 ClientModuleCreateOptions 的 loadBundle 字段,不应推断其它注入接口。boot.ts

故障、并发与运维注意

  • 注入失败:桌面脚本 onerror 拒绝该脚本的 Promise,注入链随后拒绝就绪门;run() 的等待抛错并进入自身 catch,由可选宿主回调或启动页承接。main.ts · boot.ts
  • 缺少依赖:缺 #root 在入口同步失败;桌面模式缺就绪门也在入口失败;缺模块装载 facade 则在 run() 内失败。三种错误路径不同,排查时先区分失败发生在创建入口前还是 run() 内。main.ts · boot.ts
  • 预取并发与容错:Promise.all 并行预取标为 immediately 的插件,预取失败不阻断这一阶段;正式装载的失败由下游 loader 报告,不在这里提前判定。boot.ts
  • 生命周期:run() 的异常处理并未自动调用 dispose();复用/清理入口时须注意 dispose() 才停止 drag recall 并释放 fiber。源码没有展示并发调用 run() 或与 dispose() 交错的协调机制,不应假定其可重入。boot.ts

更深入的服务端注入生成、桌面文档生成和插件装配内部实现应参阅对应的独立主题;本页对未读取的实现不作行为承诺。

Sources

(3 files)
apps/web
apps/web/src
packages/client/web/src