Deepseek-harness增加桌面版端序列:第 6 讲 · core 六包主线:一次回合的骨架
第 6 讲 · core 六包主线:一次回合的骨架
系列:我在Deepseek harness中增加了桌面版,将逐行代码解析我是怎么增加的,希望你也能创建属于自己的桌面版Agent
本讲目标:走读packages/core/六个核心包的关键导出与依赖关系——session(事件日志)、system-prompt(组装)、tools(管道)、agent(接口)、agent-loop(驱动)、scope(隔离)。目标:掌握"一次回合"的骨架与六包的依赖方向。
方式:关键类型/函数精讲(非全行)——本讲是"支线精讲"。
代码仓库:https://github.com/tslcarmack/deepseek-harness-desktop
Desktop App 运行效果图
🎯 本讲地图
scope(依赖最底层,纯库)
▲
├── session(ctx.sessions:事件日志)
├── system-prompt(ctx.systemPrompt:提示词组装)
├── tools(ctx.tools:工具管道)
└── agent(ctx.agents:Agent 接口)
▲
└── agent-loop(ctx.agentLoop:唯一实现,最上层)
依赖法则:扩展插件依赖 agent(接口),永不直接依赖 agent-loop(实现)——所以主循环可被替换。这就是"脊梁可换、接口稳定"的架构。
📁 1. scope:作用域原语(packages/core/scope/src/index.ts)
定位:非服务纯库,被 session/system-prompt 无环引用。
export function createScope(ctx: Context, key: ScopeKey, options?: CreateScopeOptions): Scope
export function scopeOf(ctx: Context): ScopeKey | undefined
export function scopeTarget(base: T, key: ScopeKey | undefined): Scoped
export function bindScopeParent(key: ScopeKey, parent: ScopeKey): ScopeParentBinding
export function scopeParentOf(key: ScopeKey): ScopeKey | undefined
export function scopeChainOf(key: ScopeKey | undefined): ScopeKey[]
精讲:
| 函数 | 作用 |
|---|---|
createScope |
为某个 key(默认是 Agent 对象)铸一个注册作用域,返回 { ctx, rawDispose, dispose } |
scopeOf |
从 ctx 取当前作用域 key |
scopeTarget |
生成带品牌的"路由接收器"——作用域过滤事件用它做 this |
bindScopeParent / scopeParentOf / scopeChainOf |
作用域继承链(子 agent 可见父级注册) |
为什么重要:一个进程跑多个 agent 时,ctx.tools.register() 到底注册给全局还是某个 agent,由作用域决定。ScopedLayers(store.ts)管理"全局层 + 精确作用域层",层空即回收。
📁 2. session:事件日志(packages/core/session/src/index.ts)
定位:Session(第 425 行)是追加式事件日志;SessionStore(第 792 行)管理多个 session。
export class Session {
// 核心方法(示意)
append(event: SessionEvent): void
deriveMessages(): Message[] // 从日志派生模型历史
fork(source, boundary?, childSessionId?): Session
}
export class SessionStore extends Service {
// ctx.sessions 的实现
}
精讲:
| 成员 | 作用 |
|---|---|
append |
追加一条 SessionEvent(turn/step/user/assistant/tool…),seq 连续 |
deriveMessages |
从日志派生模型看到的 Message[]——从不单独存历史 |
fork |
从边界复制历史生成子会话 |
adoptSessionEvent / snapshotSessionEvent(第 167/192 行) |
事件采纳与快照(lossless JSON 保证) |
核心不变量(前几讲反复出现):模型可见即已入日志。第 3 讲 desktop 的每次对话,都在这份日志上追加。
📁 3. system-prompt:提示词组装(packages/core/system-prompt/src/index.ts)
定位:ctx.systemPrompt,收集各插件的提示词段 + 工具 schema。
export interface PromptSection {
name: string
order: number
text: string
// ...
}
export interface ToolProviderResult { ... }
export interface PromptAssembly { ... }
export const PERSONA_SECTION = 'deployment:persona'
export const PERSONA_ORDER = 0
精讲:
PromptSection:一段提示词(name + order + text)。插件用systemPrompt.section(...)注册自己的段(如第 4 讲addHarnessSourceSection注册harness:source,order = -99)。system-prompt/assemble瀑布:每步请求前,把所有段按 order 排序组装成最终提示词 + 工具 schema 列表。PERSONA_ORDER = 0:persona(人设)段的默认位置——agent 级deployment:persona可遮蔽全局默认。
📁 4. tools:工具管道(packages/core/tools/src/index.ts)
定位:ctx.tools,工具注册表 + 守卫执行管道。
export interface ToolDefinition extends ToolSchema {
output: ToolOutputDefinition
execute(args: unknown, exec: ToolRunContext): Promise
finalizeContent?(...): ContentBlock[] | undefined
timeoutMs?: number
isConcurrencySafe?(args): boolean
presentCall? / presentResult?(...)
}
export interface ToolExecutionInput { callId, name, arguments, signal, ... }
export type ToolExecutionMode = { kind: 'parallel' } | { kind: 'exclusive' }
精讲(管道五段 + 守卫):
tools/pre-execute(allow/deny/ask 策略瀑布)
→ ctx.tools.guard()(单调守卫:只能拒绝,无法被后续撤销)
→ tools/execute(around 包装:超时/重试/指标,只可换 signal)
→ tools/post-execute(accept/replace/block 结果变换)
→ finalizeContent(工具定义级末次内容)
→ tools/result(只读观察冻结结果)
- 第 469-472 行:
TOOL_ABORTED/TOOL_ABORTED_BEFORE_DISPATCH两个取消码——取消语义的标准化。 - 第 494 行
ToolNotFoundError:未知工具映射UNKNOWN_TOOL,调用失败但不结束回合。
📁 5. agent:Agent 接口(packages/core/agent/src/index.ts)
定位:ctx.agents,活 Agent 句柄 + 注册表 + 创建工厂缝。
export interface AgentHandle {
agent: Agent
dispose(): Promise
}
export interface AgentFactory {
create(options: CreateAgentOptions): Promise
resume(options: ResumeAgentOptions): Promise
}
export class AgentRegistry extends Service { ... }
精讲:
| 类型 | 作用 |
|---|---|
AgentHandle |
拥有者句柄:dispose() 是能力,只有持有者能拆 agent |
AgentFactory |
创建缝——agent-loop 通过 ctx.agents.setFactory() 注册实现,消费者只依赖 ctx.agents |
CreateAgentOptions |
新 agent 的完整上下文(session 元数据、seed、per-agent options、setup 回调) |
关键设计:Agent 接口(packages/core/agent/src/types.ts)定义 send/followup/steer/inject/cancel/whenIdle 等——第 3 讲桌面端"一次对话"最终都汇聚到这些方法。
📁 6. agent-loop:主循环(packages/core/agent-loop/src/index.ts)
定位:ctx.agentLoop,Agent 接口的唯一具体实现。
export class AgentLoop extends Service implements AgentFactory { ... }
export interface AgentLoopSettings { ... }
精讲(一次回合的主循环骨架):
claim inbox 批(next-step + 一条 next-turn)
→ turn/start(入日志)
→ agent/pre-step 瀑布(改写/拒绝认领的消息)
→ step/start + user/message(入日志)
→ system-prompt/assemble + 派生历史
→ agent/request 瀑布 → llm/stream → assistant/chunk*(入日志)
→ tool/call → tools 管道 → tool/result(入日志)
→ step/end → 若工具还亏欠 → 下一 step
→ agent/turn-stopping(serial 终检)
→ turn/end(入日志)
AgentLoop implements AgentFactory:通过ctx.agents.setFactory()把自己注册为创建者。- 可替换性:UI/hooks/协议插件都依赖
ctx.agents(接口),从不 importagent-loop——想换主循环,注册一个新 factory 即可。
🖼️ 第 6 讲依赖图

图 6-1 · core 六包依赖方向与一次回合数据流
⚙️ 机制小结
- 依赖方向单向:scope → {session, system-prompt, tools} → agent → agent-loop。底层是库,上层是驱动。
- 接口/实现分离:
agent定义契约,agent-loop提供实现——换主循环不动消费者。 - 六包各司其职:日志(session)→ 组装(system-prompt)→ 执行(tools)→ 句柄(agent)→ 驱动(agent-loop)→ 隔离(scope)。
- 一次回合 = 六包协奏:认领 → 组装 → 请求 → 工具 → 落日志,每步都有 durable 事件。
- 作用域贯穿:tools 注册、事件派发都受 scope 控制——多 agent 隔离的根基。
🧪 动手验证
# 1) 数核心包规模(感受"接口轻、实现重")
cd D:\code\deepseek-harness
wc -l packages/core/agent/src/index.ts packages/core/agent-loop/src/index.ts
# 2) 验证 agent-loop 是唯一实现(搜谁 implements AgentFactory)
grep -rn "implements AgentFactory" packages/ --include="*.ts" | grep -v node_modules
# 3) 在 desktop 树里定位六包行
node apps/cli/lib/bin.js --profile desktop --dump-config | grep -E "session|agent-loop|system-prompt|tools" | head
📚 深入指引
| 包 | 关键文件 |
|---|---|
| scope | src/index.ts / src/store.ts |
| session | src/index.ts(Session@425)/ src/types.ts(SessionEventMap) |
| system-prompt | src/index.ts |
| tools | src/index.ts / src/schema.ts / src/presentation.ts |
| agent | src/index.ts / src/types.ts(Agent 接口) |
| agent-loop | src/index.ts(AgentLoop@296) |
下一讲预告:把六包串成一条线——从桌面窗口发一句话,到模型回复的完整旅程,每一步对应哪个事件、哪个源码文件。
更多推荐

所有评论(0)