夜雨聆风学习资料网

ARTICLE · 1334247

dsh-superagent 文档|007 架构

dsh-superagent 文档|007 架构

本文是该实现的开发者指南。它把 001 领域模型 到 006 部署 中阐述的概念映射到具体的类、接口和文件。请先阅读 runtime 与 Engine 部分,然后把模块表和测试表当作代码索引来使用。

核心类与胶水层

核心类通过小型接口持有领域概念与外部边界。胶水层(Glue)则把工具参数、HTTP 请求、来源(Source)载荷以及 DeepSeek Harness 事件翻译成对这些类的调用,再把结果转换成卡片、视图和消息。

flowchart TB   plugins[Source plugins] -->|SourceContext| engine   tools[Session tools] --> engine   http[Management routes] --> engine   engine[Engine] -->|Repositories| store[Store]   engine -->|Policy| policy[Policy]   engine -->|SourceScope| registry[SourceRegistry]   engine -->|Assessor| assessor[MasterAssessor]   engine -->|Executor| executor[DshExecutor]   assessor -->|decide| engine   engine -->|Outbox| outbox[Outbox]   executor -->|assignmentStarted, inputsDelivered, report| engine   settings[Settings] -->|policyChanged| engine   assessor & executor & sessions[Sessions] & outbox -->|enqueue| effects[Effects]   effects --> store 

Runtime(src/app/runtime.ts)负责构建应用、提供依赖,并掌管启动与关闭。Engine 构建它自己的内部各部分。执行器(Executor)与评估器(Assessor)会回调 Engine,因此 Runtime 为它们提供 getter,而不是要求按循环顺序构造。

Runtime

Runtime 是组合根(composition root);它不含任何领域规则。它的构造函数锁定状态目录、打开 Store,并构建每一个组件。如果构建失败,它会关闭 Store 并释放锁。

启动会注册管理路由并运行 Setup。Setup 解析完成后,它安装 Session 与执行器钩子、确保主 Session 存在、启动策略(Policy)同步、恢复被中断的派工(Assignment),并启动 pass 与副作用(effect)。ready 标志启动完成。

关闭会取消各轮(turn)、等待 pass/effect/agent 收尾、恢复进程环境、关闭 Store 并释放锁。来源插件通过窄接口 SourceHost,以 orchestrator(编排器)服务的形式被注入 Runtime。

组成
模块
作用
Setup
src/app/setup.ts
以 worker shell 实际运行 CLI 的方式探测各仓库与账号的 CLI,从而解析出仓库和账号;写入并应用 worker 的环境与命令 shim(垫片);保存 setup 发现的问题。Settings 通过它校验被修改的账号,并通过它重启(重启会再次执行 setup)
Passes
src/app/passes.ts
每次只跑一个 pass:每两秒一次,并在任何命令请求时触发——先处理到期的 effect,然后调用 Engine.evaluateDue;来源的触发(Trigger)通过它送达 Engine
Outbox
src/app/outbox.ts
Engine 的 Outbox 端口,也是回执(receipt)的唯一写入者:send 在一个事务内保存消息的回执并排队它的 reply effect,而该 effect 的处理函数通过 SourceRegistry.send 把消息发出去;当某消息所属的来源处于关闭状态时它会等待,当会话(conversation)已不存在或不再被监听时它会被丢弃
Notices
src/app/notices.ts
把领域事件和 worker 活动转换成主 Session 消息。eventNotice 处理领域通知;reportRelay 立即转发提问,把普通进展留在其工单(Ticket)上,并转发其他报告;followupRelay 指明拥有某条人类追问(follow-up)的那些工单。在某一轮进行期间到达的转发消息,会在该轮的下一步通过 DeepSeek Harness 收件箱(inbox)一并投递

Engine

Engine(src/core/engine.ts)是触发、线程(Thread)、决定(Decision)、工单和派工的唯一写入者。它从 src/core/ports.ts 接收这些端口:Repositories、一个获取当前策略的 getter、SourceScope、Assessor、Executor 和 Outbox。可选依赖是 prepareTicket、now 和 emit。

Engine 的所有方法都是同步的。每条命令在一个事务内修改它的聚合(aggregate)。evaluateDue 对每一个它所修改的评估(Eval)或工单使用单独的事务。端口在事务内部把外部工作排队;适配器(adapter)在提交(commit)之后执行它。无效请求会抛出 UserError。

Engine 只暴露一个入口点,并委派给五个内部组成部分。它掌管每条命令的事务。跨多个组成部分的命令,例如 attach、evaluateDue 和 policyChanged,会按顺序调用各个组成部分。

每个组成部分都继承 EnginePart(src/core/part.ts),后者提供配置项和共享的读取能力。组成部分之间的依赖是单向流动的:

组成
职责
依赖
Intake
来源的触发:连同其处置(disposition)只存储一次,记录到它所在的各个线程及其关联关系上,扇出(fan out)给已订阅(Subscription)的工单;除非它是一条已经抵达某个工单的人类消息,否则在其主线程(primary thread)上为其排队一个评估
Tickets
Evals
线程的各个评估:批处理、准入闸门(gate)给出的强度、行为者(actor)与依据(basis),以及给出它们的那个触发(primary);从候选(candidate)投递时起算的截止时间(deadline)及其因暂停(pause)而产生的顺延;过期评估的重新排队;候选、重放(replay)与交还(hand-back);因策略变更而重新排队;以及每个评估最终得出的决定:accept、stop、ignore 或 unavailable
Tickets
Tickets
工单的生命周期:由一次 accept 创建,附带其授权(Mandate)、订阅和第一个派工;它所跟随(follow)的线程及其关闭情况(followsDone);附加的线程与仅为上下文而跟随的线程;输入(Input)与备注(note);resume 以及 resume 所拒绝的消息;retry、restart、stop 和 end;它所丢弃的订阅;以及向受某次策略变更影响的工单发出通知
Replies
Assignments
执行器及其 worker 所上报的内容:某个派工已开始、输入已抵达它或抵达失败、某次 restart 打断了它,以及一个 worker 的报告(及其向人类提出的疑问)
Tickets、Replies
Replies
通过 Outbox 进行的渠道(Channel)写入:确认(acknowledgement)、答复(answer)、结果(result)和提醒(attention)消息,每一种都按其对应的允许规则发出
无

命令使用祈使语气的名字,例如 stopTicket;适配器上报的事实使用过去式的名字,例如 assignmentStarted。查询 candidate 使用名词。

调用方只会拿到 Engine 中属于其角色的 Pick:EngineForSources、EngineForMainSession、EngineForPeople、EngineForSettings 或 EngineForExecutor。

角色
方法
作用
Sources
receive(trigger)
把一个触发只存储一次,并在没有来源接收它时返回 stored、duplicate 或 filtered;把它扇出给订阅了其线程的那些工单,其中一条人类消息会立即 resume 一个已结束的工单(除非处理处于暂停),并在其主线程上为一个评估排队——除非它是一条已经抵达某个工单的人类消息,那样的消息仍归该工单所有
Sources
bindThread(ticketId, thread, own)
让一个处于打开状态的工单去跟随该来源的某个线程(例如某个 pull request 的线程);若该工单正是打开这个线程的一方,则 own 为真;一个已关闭的线程若被再次绑定,就重新变为打开状态。结束工单的是这些线程的关闭,而不是一份 ready 的报告
Sources
threadClosed(thread, outcome, at)
某个线程被关闭:每一个跟随它的、处于打开状态的工单都记录这次关闭,并在它对所跟随的线程都处理完毕时结束(followsDone)——即它自己打开的线程没有一个还开着;若它一个都没有打开,则它所跟随的每一个线程都已关闭。它按这些关闭中最近一次的结局而结束为 merged 或 closed——除非出现下列情形之一:关闭之后恢复的工作尚未上报 ready;或其执行器通过确认或答复,接手了某条人类追问,且这条追问是在该工单最近一份非 progress 的报告之后、或在其最后一轮结束之后才被它决定的。若此时来了一份 dropped 的报告,则该报告会像那次关闭一样让工单结束
Main Session
candidate(triggerId)
承载某个触发的那个评估,以其评估器的视角呈现
Main Session
candidateDelivered(evalId)
该评估的候选已抵达主 Session:其截止时间从现在开始计时
Main Session
decide(triggerId, outcome)
记录一个评估的决定,或为一个显式请求记录一个直接的决定。accept 会以原子方式创建工单、授权、派工、订阅和确认。一个推断出的候选若从其卡片被忽略,会先记录 ignoredFromCard,再记录所给出的理由。只要 followupOwners 仍拥有某个属于人类的追问,就只允许 stop
Main Session
attach(ticketId, triggerId, note?)
把一个触发路由到它所指向的那个工单,其执行器随后把它当作一条追问来决定,并结束承载该触发的那个评估——该评估的其他触发会等待一个新的评估;一个已结束的工单会在其授权之下立即 resume,除非处理处于暂停。一条执行器仍在决定中的追问会被拒绝,一条被路由回当初交还它的那个工单的追问也会被拒绝
Main Session
requestStatus(ticketId)
向一个处于等待状态的执行器 Session 询问其状态,并返回是否已发出询问
Main Session
note(ticketId, text)
把来自其他工作的一项事实,以文本输入的形式提供给某个工单的执行器——它不授予任何权限
Main Session、Executor
reply(request)
为一个消息按其用途所指定的渠道排队:acknowledgement、answer、result 或 attention
People
stopTicket(ticketId, reason?)
由人发起的 stop,它会撤回该授权
People
retryTicket(ticketId)
结束这样一个工单陷入停滞的派工并启动一个新的:该工单在一份 failed 或 blocked 的报告之后正等待人类,或其输入投递失败(canRetry)
People
reevaluate(triggerId)
为一个已存储的触发排队一个新的评估
People
unsubscribe(thread)
丢弃某个线程上的每一个订阅,先前跟随它的那些工单不再跟随它;每个工单仍继续决定它此前在该处收到的追问
People
dismiss(decisionId)
驳回某个 unavailable 决定所呈现的问题,这一点由该决定记录
Executor
assignmentStarted(ticketId, assignmentId)
某个派工开始运行;它处于等待的输入随即发出
Executor
inputsDelivered(ticketId, assignmentId, inputIds)
输入已抵达该派工的执行器 Session,在其中每一个都等待某个步骤(step)将其纳入
Executor
inputsRead(ticketId, inputIds)
执行器 Session 把承载这些输入的消息纳入了它的历史(一个 user/message 会话事件),其模型由此得到它们;一个曾领取它们、但在此之前被取消或失败的步骤不算数。当一份报告或一轮结束随之而来时,每个输入即计为已读取,restart 之后也如此;其工单的结束也不会使其脱手。一个在"读取记录"机制出现之前就已投递的输入,在一份报告或一轮结束跟随其投递而来时,即计为已读取
Executor
turnEnded(ticketId, assignmentId)
执行器 Session 的一轮结束了——即使在一份报告之后——前提是它没有任何东西在后台运行,或它正把该轮交接给某个人类。某个步骤先前已领取的东西,就算已被读取(inputsRead)
Executor
deliveryFailed(ticketId, assignmentId, error)
投递输入失败;工单会一直显示这一情况,直到某次投递成功、一个新的派工启动,或该工单结束
Executor
report(ticketId, report)
记录一份报告及其向人类提出的疑问;dropped 会结束该工单;ready 会完成一个不跟随任何线程的工单,并按所跟随线程各自的结局,结束一个对其所跟随的线程都已处理完毕的工单
Executor
markUnrelated(ticketId, thread, reason)
仅丢弃该工单对它认定无关的某个附加线程的订阅,并把该工单在那儿决定处理的追问连同理由一并交还
Executor
followForContext(ticketId, thread)
为一个处于打开状态的工单,仅为上下文而订阅某个相关的线程:该线程的消息会抵达这个工单,但不赋予它任何追问强度,也不会 resume 它
Executor
handback(ticketId, triggerId, reason)
记录该工单把一个曾抵达它的触发交还给系统,附带其候选所携带的理由;若此前是该工单在决定这个触发,那么在没有其他工单决定它之后,就为它排队一个新的评估;返回是否已为其排队。一个工单对每个触发只交还一次,并保留其订阅
Executor
assignmentInterrupted(ticketId, assignmentId)
某个派工在宿主(host)停止时仍在运行:一个在工作中途被打断的派工,或一个在其最近一份报告之后收到过输入的派工,会像 retry 那样重新开始——附带一条恢复备注(recovery note),以及那些已投递给它、但其执行器 Session 未纳入的输入;一个自那以后已有过报告的派工则保留其 Session。DshExecutor 会在启动时逐一上报这些情况(resume),最年长的工单优先,并再次为一个仍在等待的 launch 排队
Passes
evaluateDue()
除非处理处于暂停:为那些其排队触发正在等待的线程启动评估,每个线程只启动一个,期间排队的触发合并为一个批次;把一个已过截止时间的评估的触发重新排队,连续重新排队两次,第三次则记录为 unavailable;并把暂停期间被人类消息抵达的那些已结束工单(若其授权仍然成立)恢复。一个候选尚未抵达主 Session 的评估不会过期。处理处于暂停期间没有任何评估会过期,而一旦恢复,每个截止时间都按暂停的时长相应顺延
Settings
policyChanged(from, to, effects)
告知每一个处于活动状态的工单该变更以何种方式影响它(见 src/policy.ts 的 policyEffects:它当初被 accept 所依据的某条规则发生了变化或不再适用、其请求者可能不再可以开始工作,或不再接受主动工作),并在新版本下重启正在进行的那些评估

Engine 把批处理、授权、扇出、resume 以及提醒生命周期等规则都作为私有实现。若干共享谓词(predicate)使这些规则保持一致:

  • waitsForPerson
    (src/core/model.ts):最近的派工上报了 blocked 或 failed,且自那以后没有任何人类消息抵达。当此条件为真时,自动生成的输入与失败的 launch 都保持搁置。
  • followupOwners
    :仍然对其自身线程上收到的人类追问负责的工单。一个工单可以处于活动状态,或在没有人类 stop 的情况下已结束并等待 resume。把该消息交还会终结这种所有权。
  • settled
    :某个决定已解决该请求;stop、unavailable、routed 以及 refused-stop 这几类决定不计入。
  • ticketStage
    (src/core/stage.ts):实现 003 执行 中的阶段表。

当一个工单结束时,未读取的追问会退回评估——除非另有工单仍拥有它们,或另有处置已使其落定。已被确认、已答复以及已交还的消息被排除在外。对于没有确认处置的旧记录,outbox 的回执提供了这一事实。一份最终报告也涵盖了派工开始时正处于等待的消息,包括那个 resume 了它的那条消息。使某个工单被 stop 的那条消息不会被自动重新评估。

在一次变更之后,Engine 会发出一个来自 src/core/ports.ts 中 DomainEvents 的事件:

trigger.received、eval.started、decision.recorded、ticket.created、ticket.resumed、ticket.ended、assignment.started、assignment.ended、thread.subscribed、thread.unsubscribed、trigger.requeued,或 policy.changed。

Runtime 把 ticket.ended、trigger.requeued 和 thread.unsubscribed 转换成通知。一条投递给工单的人类 trigger.received 会变成一次追问转发。当某个工单以 merged 或 closed 结束时,pullEndNotices 会用一个稳定的 id 发送一条聊天通知。其余事件目前没有消费者。

模型(Model)

src/core/model.ts 持有领域类型,src/core/stage.ts 持有工单阶段。一个决定把原因记录为 reason——一个 Reason 列表(src/core/reasons.ts):每一项是一个 code 连同其 values,并且对于批次中的某个触发,还有其 triggerId。

核心的这些 code,例如 timeout、policyChanged、routed 和 startedTicket,都属于 REASONS,并带有供 agent 通过 reasonText 阅读的英文文案;准入闸门或某个来源 scope 会给出自己的 code,例如 not_a_member;而 said 则承载评估器或某个人写下的文字。面板按 reason.<code> 为每个 code 生成文案,被来源过滤掉的触发也以同样方式记录该来源的 code。

一个工单对每一项事实只保存一次。只要它没有 ended,它就处于打开状态;该记录保存它于何时、被谁、因何原因、以何种结局结束;它最后一次"说过"的内容是其最近一份派工报告(latestReport);其执行器 Session 以它的名字命名(见 src/executor/dsh.ts 的 workerSessionId)。

它的字段都是有类型的:origin,即赋予其评估批次以强度的那条可准入触发,以及批次本身(triggerId、triggerIds);execution,执行器所选择的(name、directory 和 assignment);delivery,最近一次失败的输入投递(error、at),直到某次投递成功、一个新的派工启动或该工单结束为止;以及 follows,即某个来源让它去跟随的那些线程,每一项形如 {thread, own, closed?}:当该工单打开了此线程时 own 为真,一旦此线程关闭,closed 就带上 at 与 outcome。只有工单会写入 follows;GitHub 来源通过 pullsOf 和 pullOf 读取它自己的 pull request(src/sources/github-pull.ts)。

一个承载触发的输入会记录它是否由人所写,waitsForPerson 读取这一点。一个线程还在 context 中列出那些已订阅、但仅为上下文而跟随它的工单,并在 expired 中统计其排队触发连续过期的评估个数;一个评估会记录它的 primary 触发及其候选何时被 delivered。

一个触发的处置记录它曾抵达的工单(通过扇出或附加)、某人的重新排队、每一个把它交还的工单(连同理由),以及其执行器曾确认或答复过它的每一个工单。

一个授权的 basis(MandateBasis)记录准入闸门是以何种方式准入其触发的。重新校验(Revalidation)会读取承载它的 source、scope 与 row、被列出的 sender、匹配到的 rules,以及 via:

via
准入依据
addressed
一个被允许的发送者向该 agent 发起了对话
followup
一条消息抵达了一个有工单的线程
admission
准入表中的一格;observation 标记一个推断出的候选
bot
一个被列出的 bot 生成了一个推断出的候选

触发和线程的 meta 属于该来源。核心不会去解释它们。

Policy

src/policy.ts 定义设置的模式(schema)、授权与评估规则。settingsPolicy(settings, scope) 每次调用都返回一个全新的、无状态的 Policy:

  • version
    、paused 和 evalTimeoutMs 描述其当前状态。
  • gate(trigger, thread, tickets)
     返回准入强度与依据。
  • revalidate(mandate)
     检查一个已存在的授权是否仍然成立。
  • admitsInferred(rules)
     只有在 auto 模式、且存在已启用的匹配规则时,才允许主动接受。

assessmentOutcome(assessment, settings, strength) 把模型的评估结果转换成 Engine 的结果。它要求对每一条已启用的规则都作出判断、落定的方向、一条匹配的规则,以及意图的证据。在 auto 模式之外,一个推断出的 accept 会变成一条"本会接受(would accept)"的记录。

policyEffects(tickets, old, now, scope) 判定一次变更影响哪些处于打开状态的工单、以及以何种方式影响。Engine.policyChanged 只通知这些工单。policyVersion 对规则、accept 模式以及各来源的策略部分(包括 bot 列表)做哈希。

Store

Store(src/app/store.ts)持有一个 SQLite 数据库。Runtime 在打开它之前先获取目录锁,从而阻止第二个进程去迁移或写入同一份状态。

它的接口按用途分组:

  • 领域仓储(Domain repositories):atomic
    ,加上 triggers、threads、tickets 和 decisions,每个都带有 get、save 以及列表查询。带索引的工作集有 threads.active()、tickets.open()、tickets.active() 和 read.subscribedThreads。
  • 读取查询(Read queries):read
     通过诸如 threadTriggers、triggerStats、search 和 droppedReplies 这些方法,为视图、工具和来源轮询提供服务。这些方法从不写入。
  • 来源元数据:threads.describe
     和 threads.patchMeta 更新归属于来源的线程元数据。
  • 设置(Settings):settings
    、saveSettings、blockRevision 和 settingsHistory。设置与提示词都使用带编号的 Versions(src/app/versions.ts)。
  • Outbox 回执:replyStatus
     和 receipts,仅由 Outbox 写入。回执操作包括 get、waiting、behind、add、settle 和 withdraw。behind 检查针对同一工单与同一会话的一条更早消息是否正在等待,且失败次数少于三次。一条被撤回的消息以 channelGone 或 channelIgnored 落定。读取查询通过 failingReplies、droppedReplies 和 droppedSince 暴露失败与被丢弃的回复。
  • 带类型的键值记录:get
    、set、delete 和 keys,使用来自 KV 的条目。

afterCommit 只在事务提交之后运行某个动作,绝不在回滚之后运行。Runtime 以记录的存储形态读取它们;schema 11 迁移会在启动时转换较早的、仍受支持的记录。

Effects

Effects(src/app/effects.ts)是持久化的副作用队列。一条命令在其事务内部调用 enqueue(kind, key, payload);执行只在提交之后才开始。

每个 kind/key 对只有一行。为一个已处于等待的相同 key 再次入队,会替换其载荷并使其立即到期,而不改变队列顺序。handle(kind, run, options) 为每个 kind 注册一个处理函数。它按最旧优先处理已到期的行,直到达到并发上限。

失败会以指数退避(exponential backoff)重试。抛出 Later 会在不计为一次失败的前提下推迟工作,例如没有可用的 worker 槽位时。start 在 setup 之后开始处理,drain 在关闭期间停止处理但保留处于待处理的行,pending/idle 则暴露队列状态。

Kind
处理函数
作用
并发(At once)
重试
assessment
MasterAssessor
把某个评估的候选卡片引导进主 Session 的一轮,然后告诉 Engine 它已被投递——由此启动该评估的截止时间
4,暂停期间不并发
2 秒起,倍增至 60 秒
launch
DshExecutor
启动一个待处理的派工:准备命令、执行器 Session、派工消息
空闲的 worker 槽位;暂停期间不启动
模型提供方或网络的一次瞬时失败会在 5 秒后重试、倍增至 300 秒,共三次;任何其他失败,以及一次失败的准备命令,都会使其工单失败。一个因暂停或因 worker 槽位占满而被搁置的 launch,会在 5 秒后再试
delivery
DshExecutor
按顺序把某个工单处于等待的输入交给它正在运行的派工
4,暂停期间不并发
2 秒起倍增至 60 秒;一个在等待 worker 槽位的,会在 2 秒后再试
relay
Sessions
把某个 worker 的报告或提问,或某人的追问,引导进主 Session 的一轮
1,按队列顺序
2 秒起倍增至 60 秒
notice
Sessions
在不唤醒主 Session 的前提下,向其注入一条通知
1,按队列顺序
2 秒起倍增至 60 秒
reply
Outbox
通过某个渠道的来源发送一条消息,且在其所属工单发往同一会话、且失败次数尚未达到三次的更早消息之后发送;撤回该消息即取消它
1,暂停期间不发送
5 秒起倍增至 300 秒;一条其来源处于关闭、或排在某条更早消息之后的,会在 5 秒后再试

每个 kind 都在其名下有一个健康状态条目(health entry)。当其中某个 effect 连续失败三次时它就上报一个错误,唯独 delivery 例外——它的失败改在其工单上显示。转发保持其顺序,通知也是如此;一个失败项会阻挡其同类中较晚的那些。

来源(Sources)

来源通过定义在 src/sources/kind.ts 的 SourceKind 扩展宿主。一个 kind 声明其标识(identity)、标题、描述、配置 schema 与 fields、派生的 facts,以及 health 条目。可选行为包括 scope、一个 channel(allowed、send、link)、threadContext、find、policy、environment 和 refreshLabels。

SourceRegistry(registry.ts)持有已注册的 kind 与已存储的实例,并实现 Engine 的 SourceScope。注册会在 HealthBoard 上认领(claim)健康条目;重复认领以及被保留的 sync: 名称都会被拒绝。一个 SourceContext 只通过其注册所认领的条目进行写入,因此注销之后的上报不会有任何效果。

注册表的配置方法包括 kind、instances、instance、validate、validatePolicy、policyPart、channelAllowed、declarations、environments、find 和 scope。find 会询问每一个支持搜索的 kind。

通用代码通过注册表访问来源的行为。send(message, commands) 调用该 kind 的渠道;若该 kind 未安装,则返回 sent、gone 或 waiting。link 解析其渠道链接。threadContext 把一个 kind 的读取器与其配置配对,或在该 kind 没有读取器时返回空。无效的存储配置会使该实例被禁用。

插件会收到 SourceHost:注册表以及 context(kind)。kind 本身使用一个更窄的 SourceContext,由 src/app/source-context.ts 中的 sourceContextOf 构建。

该 context 提供部署与命令、setup 就绪情况(ready、configured、problem)、配置、receive、atomic 以及带类型的 state。它还暴露线程读取与元数据写入、工单读取与绑定/关闭命令、触发读取、通过 Outbox 的 notify,以及针对已声明健康条目的 report。

DshExecutor

DshExecutor(src/executor/dsh.ts)为每个工单提供一个 Session 和一个目录。start 与 deliver 分别把 launch 和 delivery effect 入队。stop 通过 Store.afterCommit 取消该轮,因此一次被回滚的结束不会停止 worker。

prepare(ticket, outcome) 提供 Engine 的 prepareTicket 钩子:短名称、目录以及 ticketExecution 派工文本。workerSessionId(ticket.id) 推导出 Session id。

WorkerSlots 在一次 launch、或一次会唤醒空闲 worker 的 delivery 之前预留容量。一条人类消息可以使用超出 maxWorkers 的一个槽位,且唤醒优先于 launch。activeCount 为面板提供负载信息。

临时的提供方与网络失败会获得三次重试。launch 使用 effect 退避;失败的轮会分别在第 15、30 和 60 秒后收到续跑消息。inputMessage 依据当前工单状态渲染每一个输入,mandateInstructions 为推断出的工作添加匹配规则的限制。

install 订阅 Session 事件;dispose 取消订阅。一轮的结束通常上报 waiting,除非是在就绪之后,或是在需要人类操作之时。错误上报为 failed。在启动时,resume 通过 assignmentInterrupted 上报被中断的派工,并重新排队那些不等待人类的待处理 launch,最年长的工单优先。

MasterAssessor

MasterAssessor(src/app/assessment.ts)让主 Session 能够评估主动(proactive)候选。request 把一个评估 effect 入队;release 在其评估结束时丢弃缓存的状态。

它的 context、read 和 decide 方法服务于 orchestrator_eval、orchestrator_eval_read 和 orchestrator_eval_decide。它们通过 SourceRegistry.threadContext 提供有界的线程上下文,外加仓库上下文和决定提交。一个没有上下文读取器的 kind 只提供触发与批次。

assessmentOutcome 为 Engine.decide 转换模型的回答。Evals 会针对当前策略再次校验推断出的接受。

Settings

Settings(src/app/settings.ts)读取和写入带修订版本(revision)的设置块。current 与 view 读取它们;save(block, value, revision, origin) 校验一次保存;pause(paused, origin) 修改独立的 paused 设置。

每一次保存都声明其来源(origin):panel、tool 或 sync。只有 panel 才能更改执行环境,或更改 SETUP_FIELDS(src/deployment.ts)中列出的字段。

一次策略版本变更会计算 policyEffects、记录哪些工单被通知,并调用 Engine.policyChanged。syncable 和 sync(id) 刷新某个来源的策略部分。refreshLabels(id) 刷新显示名称。

Prompts

Prompts(src/app/prompts.ts)把配置以 Versions 形式存储。current、version 和 history 读取它们;validate、preview 和 save(value, baseRevision, source) 检查并保存它们。compose 和 skills 构建 Session 文本。

builtInParts 列出覆盖键、角色、默认文本以及必需的模板占位符。每一处覆盖都存储团队 text 和一个 base:其原始默认的 builtInBase 键。保存会把该默认保留在 KV.prompts.builtIn 之下。随后 defaultChanged 会报告发生变化的默认、在有旧文本时给出其旧文本,以及被移除的部分。

校验会从一个被省略的 base 继承已保存的覆盖,丢弃那些已存在于已保存数据中的被移除部分,并拒绝空白文本或其他未知键。这使得保存工具能够对其读取到的配置做往返(round-trip)。environmentSection 和 policySection 只使用各自的键、替换一次模板取值。没有覆盖的配置使用默认值。

Sessions

Sessions(src/app/sessions.ts)为两种角色管理 DeepSeek Harness Session。ensure 用该角色的模型创建一个 Session。install 跟随 agent 事件,并为受管理的 Session 注册提示词、技能和工具。

prompt 对每个请求 id 只投递一次。relay 和 notify 把主 Session 消息入队,由 relay 和 notice effect 处理。src/app/notices.ts 定义其内容;delivery 检查某条消息是否已抵达。

每一次模型请求都会把该 Session 绑定到当前的提示词修订版本。它所使用的修订版本被持久存储:promptVersion 标识当前生效的那个,mayRead 限制 worker 可以查看哪些版本。注册情况按每个 agent 一张 map 来跟踪。

status 和 promptVersion 服务于提示词页面。保存都经 Prompts.save 进行,并通知主 Session。modelRetry 暴露模型额度重试状态。

胶水层(Glue)

胶水层
转换的内容
src/app/runtime.ts
组合根:用各部分所使用的接口构建它们,注册路由与来源宿主,并启动和停止 dsh-superagent
src/app/notices.ts
把领域事件与 worker 报告,转换成给主 Session 的通知与转发
src/app/tools/main.ts
、src/app/tools/executor.ts
把主 Session 与执行器 Session 的工具参数,转换成对核心类的调用,并把其结果转换成卡片
src/app/http.ts
、src/app/views/、src/app/api.ts
把 HTTP 请求转换成调用,把 Store 读取转换成面板的视图;api.ts 为面板给每条路由加上类型
src/app/resources.ts
、src/app/links.ts、src/app/cards.ts
把资源名转换成读取,把源消息转换成链接,把记录转换成供模型使用的紧凑卡片
src/sources/github*.ts
、src/sources/feishu*.ts、src/sources/poll.ts
把 GitHub 与飞书载荷转换成触发和线程事实,把消息转换成 CLI 调用
src/deployment.ts
、src/setup.ts、src/infra/
setup 探测与问题、CLI 传输、agent 的进程环境

依赖规则

  • 显式、窄依赖。
     每个类都收到一个点明其所用内容的接口,多数是其他类的 Pick 类型:面向核心类的 ExecutorHost、AssessorHost、SessionsHost、SettingsHost、SetupHost、PassesHost 与 OutboxHost,面向胶水层的 HttpHost、MainToolsHost、ExecutorToolsHost、ResourceHost、LinkHost 与 SourceContextDeps,而 Engine 则以其调用者角色的 Pick 呈现。Runtime 只在这种接口之后传递自身。一个来源插件注入 orchestrator 服务,其类型把它命名为 SourceHost:注册表以及 context(kind);其 kind 的代码只能看到自己的 SourceContext。
  • 谁能 import 谁。src/core
     除了 Node 的哈希之外不 import 自身以外的任何东西,也不做任何 I/O。src/sources 不从 src/app import 任何东西。面板只从宿主侧 import 类型,外加 src/admission.ts、src/limits.ts、src/setup.ts、src/navigation.ts、src/sources/host.ts、src/core/errors.ts、src/core/reasons.ts 这些模块的普通值,以及 src/app/api.ts 的过滤器列表。
  • 每种状态只有一个写入者。
     每一类状态都只有一个写入者,其余各方要么读取它,要么向该写入者发出请求。
状态
写入者
触发、线程、决定、工单、派工
Engine
某个来源的读取位置,以及它在其线程上保存的 meta(label、url、facts 及其自有键)
该来源,通过 SourceContext
设置及其历史
Settings
提示词版本
Prompts
effect 行
Effects
Outbox 回执
Outbox:Engine 的消息与来源的 notify 都经它的 send 送出,其 reply effect 标记它们
一个健康条目
在 HealthBoard(src/infra/health.ts)上认领它的那个部分,它会拒绝第二次认领:runtime 是 Runtime(Passes 通过它上报),evals 是 Runtime 在每次 pass 之后,models 是 Sessions,每个 effect kind 由 Effects,worker 是 DshExecutor,某个来源 kind 的条目由 SourceRegistry 为该 kind 的来源,sync:<id> 是 Settings

来源只能通过 SourceContext.tickets.bindThread 和 threadClosed 来修改工单,二者都会调用 Engine。 - 请求与事实。 一个"请求某事"的方法以祈使动词加其宾语命名,例如 stopTicket 或 bindThread,且可能被拒绝;一个"记录适配器做了什么"的方法以过去式命名,例如 assignmentStarted 或 deliveryFailed。每个宿主都以它所需的角色类型收到 Engine。 - 带类型的字段,以及来源数据归来源。 代码读取工单与授权的带类型字段。来源不在工单上保存任何东西:某个工单跟随其哪些线程记录在工单的 follows 里,而它关于某个触发或线程所保存的内容则放在它们的 meta 里。 - 固定的拒绝。 一个人或一个 agent 能够引发的拒绝,是来自 src/core/errors.ts 中 USER_ERRORS 的 UserError(code, values)。管理 API 对 missing code 用 404 应答,对其余的用 400,面板按其语言为每个 code 生成文案。setup 问题则用 SetupError code(src/setup.ts)和固定的健康文案 HealthCode(src/sources/host.ts),并以同样方式生成文案。

来源专属依赖

一些通用模块仍直接引用 GitHub 或飞书:

  • Outbox 施加飞书聊天的字数上限。
  • src/app/links.ts
     解析飞书消息链接与 repliedTo。它与 Runtime 都用 feishuSettings 读取设置。
  • src/app/resources.ts
     读取 feishu/ 与 github/ 原件;src/app/cards.ts 在工单卡片中使用 pullOf。
  • Runtime 为 merged 或 closed 的工单发送 pullEndNotices。
  • 执行器工具用 readPull 和 claimPull 绑定所报告的 pull request,使用 Runtime 暴露的 GitHub 上下文。
  • MasterAssessor 用 readRepositoryPath 读取仓库文件。
  • Sessions 与 Prompts 只为 GitHub 和飞书注册内置技能。迁移会转换它们的 schema-10 记录。
  • Setup 只探测 GitHub 和飞书的账号字段(SETUP_FIELDS)。只有那些来源会通过 SourceContext.problem 收到账号 setup 问题。

模块

路径
职责
src/index.ts
主插件入口:读取机器设置,启动 Runtime,并交给它一次重启——保存某个来源账号时会用到这次重启
src/deployment.ts
插件的 Config 与 Machine;resolveDeployment 与 resolveFeishuBot 像执行器 shell 那样运行每条 CLI,从来源设置中解析出仓库与账号;SETUP_FIELDS 列出 setup 读取的来源字段。代码不点名任何个人、账号或组织
src/setup.ts
setup 问题:SetupError,带一个 code、修复它的地方,以及日志、主 Session 与面板共用的英文文案
src/policy.ts
策略:按块划分的设置模式、策略版本、准入闸门与授权重新校验
src/admission.ts
准入表的取值、每一格的选项与默认值,与面板共享
src/limits.ts
人所设定的取值的上下限,由设置模式来校验、并由面板的字段来提供
src/kv.tsKV
:每一个 kv 记录族,连同其键、其值的类型及其所有者,以及 Store 与 SourceContext.state 所采用的 Key 和 Family 类型
src/navigation.ts
一旦宿主的目录列出某个 DeepSeek Harness Session,就从面板打开它
src/core/model.ts
领域类型,其中包括带类型的工单与 MandateBasis,并带有 threadKey、ticketOutcome、reached、followupOwners、settled、waitsForPerson、liveTicket、runningAssignment、latestAssignment 和 latestReport
src/core/ports.ts
端口:Repositories、Policy、SourceScope、Assessor、Executor、Outbox,以及领域事件
src/core/engine.ts
Engine,核心的入口点,以及角色接口 EngineForSources、EngineForMainSession、EngineForPeople、EngineForSettings 和 EngineForExecutor
src/core/part.tsEnginePart
,Engine 各组成部分的基类,以及 EngineOptions
src/core/intake.tsIntake
:来自来源的触发,交给其工单;除非一条人类消息已经抵达某个工单,否则交给评估
src/core/evals.tsEvals
:线程的各个评估及其决定
src/core/tickets.tsTickets
:工单的生命周期、某个工单所跟随的线程、订阅与输入
src/core/assignments.tsAssignments
:执行器的事实及其 worker 的报告
src/core/replies.tsReplies
:Engine 的渠道写入
src/core/stage.tsticketStage
 与 canRetry:003 执行 中的工单阶段
src/core/errors.tsUserError
 与 USER_ERRORS
src/core/reasons.tsReason
、REASONS 与 reasonText:以 code 表明核心为何记录一个决定、或将一个触发搁置
src/core/ids.tskey
:由输入派生的 id,输入相同则 id 相同
src/core/memory.tsMemoryRepositories
:Store 仓储的内存实现,遵循 Store 的顺序并带有其嵌套回滚,用于领域测试
src/core/index.ts
核心的导出
src/app/runtime.ts
Runtime,组合根,连同其 start 与 stop
src/app/setup.ts
Setup,以及 setupChanged——判断一次保存是否更改了 setup 读取的某个字段
src/app/passes.ts
Passes,pass 调度器
src/app/outbox.ts
Outbox:Engine 的 Outbox 端口、回执,以及发送这些回执的 reply effect
src/app/notices.ts
主 Session 的通知与转发:eventNotice、reportRelay、resultRelay 和 followupRelay
src/app/store.ts
Store,以及 Receipt——outbox 中的一条消息
src/app/store-read.tsstoreReads
:Store 供视图、工具和来源轮询使用的读取查询,从不写入
src/app/migration.ts
schema 11 迁移:从 schema 10 出发的一个步骤,把每一个存储记录以其当前形态写回
src/app/effects.ts
Effects
src/app/versions.tsVersions
:以带编号的快照保存记录、并附一个指向当前快照的指针,供设置与提示词使用
src/app/cache.tsTextCache
:在某个大小限额内把文本保存在内存中,最久未使用的先被淘汰
src/app/settings.ts
Settings
src/app/settings-history.tspolicyHistory
:每一个已保存的设置修订版本,连同它相比上一版本改了什么
src/app/prompts.ts
Prompts、builtInParts(一个团队可覆盖的内置部分),以及 promptParts(某角色的提示词各节的顺序)
src/app/context.ts
内置提示词文本,从 prompts/ 读取,以及 Sessions 读取的转录条目
src/app/skill-files.ts
打包文本:promptFile——把一个文件中一句一行的内容合并成段落,以及随附的技能
src/app/sessions.ts
Sessions
src/app/delivery.tsAgentDelivery
:对每个请求 id 只向某个 DeepSeek Harness agent 投递一次消息,即使在 Session 被恢复之后亦然
src/app/retry-health.tsRetryHealth
:哪些 Session 在等待模型额度重试,供 models 健康条目使用
src/app/role-models.ts
解析评估与执行器模型,并用某角色的模型为新 Session 设定初始值
src/app/assessment.ts
MasterAssessor、评估模式,以及 orchestrator_eval 作答的窗口
src/app/execution-environment.ts
执行环境:它的变量、某个工单的取值、目录根,以及提示词该节的各行——由 src/app/prompts.ts 填入其模板
src/app/source-context.tssourceContextOf
:每个来源 kind 的 SourceContext
src/app/links.tssourceLink
、某个触发消息的页面,以及 candidateCard
src/app/cards.ts
供模型使用的触发、工单、线程与决定的紧凑卡片、封闭的工单卡片状态集合 TICKET_STATES,以及 textPage
src/app/resources.tsRESOURCES
 与 readResource:每种角色下 orchestrator_read 所读取的内容
src/app/tools/tool.tsTool
 类型、readTool、两种角色各自的 orchestrator_read,以及 AcknowledgementSchema
src/app/tools/main.ts
主 Session 的工具
src/app/tools/executor.ts
某个执行器 Session 的工具
src/app/http.ts/orchestrator/api
 路由:同源检查、请求限额,以及每种失败对应的一个状态码
src/app/api.tsApi
:每条路由及其参数、查询、请求体和响应,以及各列表的过滤器;它只 import 类型,因此面板可以把它打包进来
src/app/views/text.ts
视图文本的两种形态:单行的纯文本 preview,以及不含 HTML 的 markdown;plain 与 readable
src/app/views/records.ts
各视图共享的行:触发、工单、线程、决定与渠道链接,以及 ownersOf——把追问的所有者作为一次请求读取
src/app/views/tickets.ts
按阶段划分的工单列表、某个工单的页面、授权依据的事实,以及执行器的负载
src/app/views/threads.ts
线程列表、某个线程的时间线、搜索,以及决定列表
src/app/views/sources.ts
来源状态:每个来源实例的状态、其最近一天的各项计数,以及它在那一天过滤掉与丢弃掉的内容
src/app/views/settings.ts
设置页面的视图:每一块连同其修订版本、每个来源的设置连同其 kind 的表单,以及执行环境
src/app/views/overview.ts
概览:需要人类介入的工单、问题、最近一天的流转,以及执行器的负载
src/executor/dsh.ts
DshExecutor、WorkerSlots、ticketExecution、inputMessage 与 mandateInstructions
src/executor/background.ts
DeepSeek Harness 中某个 worker 的后台工作,其 Session 拥有的 shell 任务(ctx.jobs)及其子代理(ctx.subagents):runningJobs、untilQuiet、killJobs、collectJobs、stopSubagents、stopBackground、until,以及 cancelled——其子代理被停止打断(从而取消了其轮次)的那些 worker
src/infra/commands.tsCommands
,CLI 传输:run;api 与 pages,限定在所配置的仓库内并经过校验;作为 bot 的 lark;git。CliError 携带某命令输出所报告的 code,例如 GitHub 的 HTTP 状态;一个不打印任何内容的命令会以它的结束方式失败,而绝不会带上其参数而失败;validItems 会跳过一个列表中的畸形条目并计数
src/infra/environment.ts
agent 的进程环境:命令 shim、git 配置与凭据助手、runAsWorker,以及对环境和某个工单目录的检查
src/infra/paths.ts
包的根目录,即打包文件所在之处
src/infra/text.ts
词数统计与聊天消息上限、fill 与 sameName
src/infra/health.tsHealthBoard
,每个部分的健康条目,各自被其唯一的写入者认领
src/sources/kind.tsSourceKind
 协议
src/sources/registry.tsSourceRegistry
src/sources/host.tsSourceHost
、SourceContext、Health、health,以及固定的健康文案
src/sources/poll.ts
GitHub 与飞书轮询所共用的部分:POLL_HORIZON_MS、polled、cursorAt,以及 pollThreads——它把一次失败以 pollError 保留在其线程上
src/sources/github.ts
GitHub 的 kind 与插件入口,以 ./github-watch 导出:一轮对通知的轮询、处于打开状态的工单的 pull request 及其 CI,以及被订阅的线程
src/sources/github-trigger.ts
把 GitHub 的评论、评审(review)、Issue 正文和 CI 变化转成触发,以及 GitHub 地址
src/sources/github-pull.ts
pull request 规则:某个工单跟随哪些 pull request(claimPull、observePull)、它上报给 Engine 的关闭,以及 pullEndNotices——某个以 merged 或 closed 结束的工单的聊天通知
src/sources/github-channel.ts
GitHub 渠道:评论与评审 thread 回复,sent 或 gone
src/sources/github-read.ts
CI 快照、仓库读取、github/ 资源,以及 githubThreadContext:按页读取某个 Issue 或 pull request 的对话
src/sources/feishu.ts
飞书的 kind 与插件入口,以 ./feishu-watch 导出
src/sources/feishu-trigger.ts
把飞书消息转成触发,用同一条规则处理实时事件、历史页面与话题页面
src/sources/feishu-loops.ts
飞书循环:事件消费者、历史恢复、聊天名称,以及对被订阅话题的轮询
src/sources/feishu-channel.ts
飞书渠道:话题回复,sent 或 gone
src/sources/feishu-read.tsfeishu/
 读取,用于某个触发的消息及其根(root)、父(parent)与话题,以及 feishuThreadContext——把它们连同其图片提供给评估
src/sources/admission-context.ts
评估起始时有界的窗口:某个触发所在线程的前几页(经由其 kind 读取),以及该触发周围的消息
src/sources/feishu-image.ts
把某条飞书消息的图片作为附件读取给正在评估的 Session
src/client/index.tsx
客户端插件入口:侧边栏入口、主面板,以及 DeepSeek Harness 插件管理器中的页面
src/client/app.tsx
面板:头部、搜索、术语表、标签页,以及当前路由的页面
src/client/provider.tsx
每个页面从其宿主读取的内容:文案、调用、导航、通知、状态轮询,以及 restart——在 dsh-superagent 重启期间快速读取状态
src/client/api.ts
对管理 API 的调用与轮询,由 src/app/api.ts 加上类型,以及评估状态的决定过滤器
src/client/route.ts
标签页、设置分组、parseRoute 所读取的带类型路由,以及记录了最近一次打开的设置分组的导航记忆
src/client/overview.tsx
概览
src/client/source.tsx
、trigger.tsx、eval.tsx、execution.tsx
每个阶段一个文件:它的状态,以及(若有)它的设置
src/client/thread.tsx
、ticket.tsx
记录页面:某个线程连同其时间线,以及某个工单连同其授权、输入与派工
src/client/settings.tsx
Settings 标签页:分组导航与当前屏上的分组
src/client/settings-kit.tsx
设置块的构件:各阶段分组共享的设置、useBlockDraft、各字段(其中含某个来源 kind 的字段),以及保存的历史
src/client/prompts.tsx
Prompts 设置:页面持有它读取到的视图、其编辑器持有草稿,而 Edit、Full prompt 与 History 标签页都由它们渲染;某个内置部分的编辑器会显示一个发生变化的默认,并保留或丢弃该覆盖
src/client/prompt-config.ts
以纯函数形式提供:按节划分的提示词配置、按团队各节划分、技能名称、提示词视图所给出的内置部分(part/<key>,连同 withOverride)与各部分标题,以及完整提示词的逐行 diff
src/client/deployment.tsx
Deployment 设置,同样显示在 DeepSeek Harness 的插件管理器中
src/client/draft.tsx
设置草稿:draftReducer、草稿的状态及其与某个较新已保存修订版本的合并、saveBarText、保存栏那一行的文案,以及保存栏本身
src/client/controls.tsx
DeepSeek Harness 控件的本地副本,遵循其标记结构、键盘行为与设计令牌,因为该 bundle 不 import @deepseek-ai/dsh-client-ui-primitives;它们不读取任何 context
src/client/widgets.tsx
各页面共享、读取面板 context 的小组件:分区、页面头部、加载状态、时间、链接、需确认的操作、setup 检查,以及 Markdown
src/client/rows.tsx
各页面共享的行:一条工单、一条线程与一条决定,一个线程的 chip,以及对连续自动消息的折叠
src/client/markdown.ts
报告与提问所用的 Markdown 子集,以及消息中裸写的地址——以块级与行内片段呈现
src/client/text.ts
按键取文案,以及领域取值、原因、发送者与时间的措辞
src/client/marks.ts
视图所分类各状态的标记:某个工单或线程的阶段、某个来源的状态、某个决定的结局——每个都以一个圆点、一个图标、一种色调和一个标签呈现
src/client/locales.ts
两种语言的全部文案,以及 001 领域模型 的术语表
src/client/client.css
、css.d.ts
面板的样式表,以文本形式加载

src/sources/github.ts 与 src/sources/feishu.ts 同时也是这两个来源插件的入口,由 tsdown.config.ts 构建,并在 package.json 中以 ./github-watch 和 ./feishu-watch 导出。该 bundle 的 cordis.patch.yml 插入这三个插件,其中飞书处于禁用状态。

测试

测试
覆盖
tests/core-engine.spec.ts
在内存仓储上测试领域规则:接收、评估、扇出、resume 与 stop、被中断的派工、暂停、策略变更、报告与结局、附加、回复、失败的投递,以及被驳回的问题
tests/stage.spec.ts
工单的阶段,逐行测试
tests/policy.spec.ts
来源 scope、准入闸门、准入表、授权重新校验、策略版本、设置迁移,以及评估结局
tests/registry.spec.tsSourceRegistry
:实例、校验、策略各部分、渠道与线程上下文
tests/sources.spec.ts
在 SourceContext 上的飞书与 GitHub 来源:转换、轮询与读取位置、pull request 规则、健康状态
tests/commands.spec.ts
以部署的账号身份进行的 CLI 调用、各渠道的应答,以及限定在某个仓库范围内的 GitHub 调用
tests/feishu-read.spec.ts
、tests/github-read.spec.ts
飞书与 GitHub 的读取
tests/admission-context.spec.ts
、tests/feishu-image.spec.ts
评估的线程窗口,以及飞书图片读取
tests/assessment.spec.ts
MasterAssessor 为每个评估保留什么,以及它何时放手
tests/background.spec.ts
等待某个 worker 的 shell 任务与子代理:一次把它唤醒的结束,以及一个停止的宿主
tests/bundle-patch.spec.ts
该 bundle 的补丁,按 DeepSeek Harness 读取它的方式读取,以及它所定义的执行器 Session 的 agent 预设
tests/effects.spec.ts
effect 队列:合并、限额、带退避的重试、健康状态与关闭
tests/passes.spec.ts
Passes:一次只跑一个、在某次 pass 期间被请求的那些会合并成其后的一次、以及一次失败的 pass 所上报的 runtime 健康状态
tests/outbox.spec.ts
在一个运行中的 Runtime 上的 Outbox:在处理处于暂停、或其来源未安装期间,消息会等待
tests/cache.spec.tsTextCache
tests/queue.spec.ts
Runtime 经由 effect 队列所进行的工作,以及较早版本所存储的内容
tests/followups.spec.ts
某人在一个工单的线程上发出的追问:它给主 Session 的通知、对其施以操作的工具、一次追问潮(burst)的代价;一个 worker 的交还与上下文跟随,以及主 Session 的备注
tests/health.spec.ts
每个部分一个健康条目,各自被其下一次成功所清除
tests/delivery.spec.tsAgentDelivery
tests/retry-health.spec.ts
、tests/role-models.spec.ts
模型额度重试,以及两种角色的模型
tests/runtime.spec.ts
在模拟的 DeepSeek Harness 宿主上运行的整个应用,经由工具与 HTTP
tests/tools.spec.ts
各 Session 的工具:每种角色下 orchestrator_read 读取的内容,以及其他主 Session 工具的形状与检查
tests/runtime-concurrency.spec.ts
相互重叠的工作:一次 pass 期间的触发、在投递与 launch 期间发生变化的工单
tests/http.spec.ts
同源与回环检查、每种失败的状态码、请求体上限、路径参数与查询
tests/settings.spec.ts
与某次模型或账号检查相竞争的设置保存、被一次保存抢先到的一次策略同步,以及对暂停、一次同步、一个块的拒绝
tests/views.spec.ts
管理视图从 Store 派生出的内容,例如一条被丢弃消息所归属的线程
tests/timeline.spec.ts
某个线程的页面与某个触发的邻居,按时间再按 id 排序,且每一个已接收触发的时间都统一为一种形态
tests/migration.spec.ts
schema 11 迁移:每一个存储记录都以其当前形态呈现,以及对更早 schema 的拒绝
tests/store.spec.ts
仓储及其工作集,在 SQLite 与内存中皆然,kv 区,且 KV 的每一个键都只在 src/kv.ts 与迁移中拼写;设置历史与查询
tests/prompts.spec.ts
提示词的校验、装配与版本
tests/cards.spec.ts
内置文本所必须陈述的规则,逐文件测试
tests/model-surface.spec.ts
模型所读取的一切:工具、提示词、技能与输入,对照工具模式、执行环境与术语表加以校验,并在每种角色的大小预算之内
tests/deployment.spec.ts
、tests/environment.spec.ts、tests/index.spec.ts
插件配置与 setup、agent 的进程环境、插件入口
tests/service-script.spec.ts
针对伪造的 ps、tmux 与 dsh 测试 scripts/dsh-superagent
tests/client.spec.tsx
、tests/navigation.spec.ts、tests/controls.spec.tsx
客户端插件的注册与轮询、Session 导航与导航记忆、本地控件,以及面板的 import(它们之间没有环)
tests/panel-overview.spec.tsx
、panel-source.spec.tsx、panel-trigger.spec.tsx、panel-eval.spec.tsx、panel-execution.spec.tsx
面板的每一个页面,针对一个按路由作答的伪造管理 API
tests/panel-settings.spec.tsx
、panel-prompts.spec.tsx、panel-deployment.spec.tsx、panel-panel.spec.tsx
设置分组、草稿与历史、提示词、Deployment 页面,以及面板的搜索、术语表与导航记忆
tests/draft.spec.ts
、tests/prompt-config.spec.ts、tests/markdown.spec.ts、tests/marks.spec.ts
草稿的状态、合并与保存栏那一行;按节划分的提示词配置与逐行 diff;Markdown 子集及其链接;视图所分类每一种状态的标记
tests/locales.spec.ts
、tests/styles.spec.ts
两种语言的文案,以及样式表
tests/class-docs.spec.tssrc
 中的每一个类前面都有一个 JSDoc 块
tests/docs.spec.ts
带编号的文档只链接到更早的那些文档、每一个本地链接都能解析、没有任何文档带有合并冲突标记,并且本文档的表格列出了每一个模块与每一个测试

tests/fixtures/ 存放多个测试共享的内容:host.ts,一个模拟的 DeepSeek Harness 宿主;panel-server.ts 与 panel.tsx,伪造的管理 API,以及围绕一个页面的面板;deployment.ts,一个已解析的部署;git.ts,测试所启动的每一个 git 的环境,其中不点名任何仓库;以及 async.ts。

持久化

一个 SQLite 文件,<stateDir>/orchestrator.sqlite,只有 Store 会打开它。

表
存放内容
写入者
trigger
每一个触发,其时间 at 采用 Date#toISOString 所给出的那一种形态,在 Engine 接收它时设定,从而使时间可按文本比较
Engine
disposition
每个触发去了哪里:filtered 连同该来源的原因 code、其评估与决定、它经由扇出或附加所抵达的工单、它是否被重新排队、把它交还的那些工单(连同各自理由),以及其执行器曾确认或答复过的那些工单
Engine
trigger_thread
每个触发位于哪些线程上,连同其时间,以及它是否由人所写——从而读取某个线程的触发时无需扫描全部触发,按时间再按 id 排序:某线程时间线的一页从 <at>|<id> 游标续读,共享同一时间的触发不会被跳过
Store,随每个触发
thread
线程:订阅、仅为上下文而跟随它的工单、排队的触发、正在进行的评估、其过期次数,以及 meta;生成列 active(在触发排队或评估运行时置位),以及 subscribed(当某个工单订阅它时为其来源)
Engine;来源写入 meta
ticket
工单,连同其授权、origin、execution、派工、渠道、输入及其所跟随的线程;生成列 open(在它尚未结束时置位),以及 active(在输入尚未投递、或最近一个派工处于 pending 或 running 时置位)
Engine
decision
决定,按线程组织
Engine
effect
effect 队列:按 kind 与 key 记录载荷、其顺序、失败的尝试(连同最后一次错误),以及其到期时间
Effects;schema 11 迁移会把较早版本为主 Session 保留的内容排队
outbox
每一条发往渠道的消息的回执,按 id 记录,附带其所属工单与其 status——pending、sent 或 dropped;保存它是为了一个 id 绝不会被排队两次;对于一条被丢弃的消息,还会记录其丢弃时间与原因 code,体现在生成列 dropped 上。一条正在等待的消息会有一个 reply effect,其中保存其尝试次数
Outbox
kv
其余每一项记录,按键组织
命名该记录的那个部分

生成列由其所在行的 JSON 计算得出,在其集合之外为空,且每个生成列都针对其集合建有索引,因此一次 pass 只读取处于活动状态的工作。一个早于某列而存在的数据库,会在 Store 打开它时获得该列。

KV(src/kv.ts)定义每一项键值记录的所有者、键与类型。所有读取者与写入者都使用这些条目;只有迁移代码会点名更早的键。

记录
键或内容
Schema
当前的 schema 版本
设置版本
settings-revision
、settings-version:<n>、settings-block-revision:<block>;快照包含来源、策略版本与受影响的工单
提示词版本
prompt-revision
、prompt-version:<n>
主 Session
master-id
Session 提示词历史
session-prompts:<session id>
来源状态
读取位置,例如 github-cursor:<number>、PR 的 CI 指纹,以及已见过的飞书话题消息;键以 kind 名开头,或以 thread- 加上一个线程 key 开头
显示名称
source-labels:<source id>

驳回存储在其决定上。输入文本在读取时由工单渲染得出,因此二者都不需要单独的键值记录。

读取缓存使用有界的内存 TextCache 实例,并在重启时丢失。某个 Session 在逐页翻阅一条来源原件记录或提示词预览时,必须在重启后从 offset 0 重新读取。在接收之后解析出的飞书链接以同样方式缓存;基于链接的搜索只有在当前进程存续期内读取过那条链接之后才能找到它。

目前尚没有任何东西会删除旧记录,因此以下各项会无限增长:trigger、disposition 和 decision 的行;已结束的工单及其线程;outbox 的回执;以及在 kv 中,每一次已保存的设置修订版本与提示词版本、每个 Session 工作于其下的提示词修订版本,以及各来源按线程、按 Issue 或 pull request 记录的那些记录。

SCHEMA_VERSION 只增不减。一个版本更新的数据库会拒绝启动;src/app/migration.ts 会在启动时对预发布的 schema 10 数据库升级一次,并拒绝更旧的数据库。

面向模型的接口

主 Session 工具

主 Session 共有 14 个应用工具:

用途
工具
读取与路由
orchestrator_read
、orchestrator_route
管理工单
orchestrator_ticket_retry
、orchestrator_ticket_request_status、orchestrator_ticket_note
重新评估
orchestrator_trigger_requeue
评估主动工作
orchestrator_eval
、orchestrator_eval_read、orchestrator_eval_decide
沟通与退订
orchestrator_reply
、orchestrator_thread_unsubscribe
配置
orchestrator_settings_save
、orchestrator_policy_sync、orchestrator_prompts_save

orchestrator_route 按顺序接受 1–20 个决定。每一次 accept、attach、stop 或 ignore 都作为一条独立的 Engine 命令运行。它也允许直接从某个推断候选的卡片忽略它。orchestrator_ticket_note 把一项事实作为上下文而非权威(authority)发送出去。

设置保存会选择 policy、eval、executor、pause 或 source;最后一种还要点名其来源 id。一次带 preview: true 的提示词保存会合成文本而不保存。

主 Session 还拥有 DeepSeek Harness 的 skill、bash、read、read_image、job_output、job_list 和 job_kill。避免读取代码是一条提示词规则,而非工具层面的限制。

预设与等待上限

新建的主 Session 使用 dsh-superagent-router(ROUTER_PRESET)。shell 命令与任务等待在 5 秒后返回。如果在首轮之前 Web UI 选定了另一个预设,Sessions 会恢复为本预设;对 worker 则恢复为 WORKER_PRESET。

src/app/sessions.ts 中的 noWait 守卫会拒绝这类主 Session shell 命令:在命令位置调用 sleep 的、shell timeoutMs 超过 30 秒的,以及 job_output 等待超过 5 秒的。报告与任务结果会自动抵达,因此等待只会延迟即将到来的候选。

运行于 standard 预设上的既有主 Session,同样需要为前台命令与任务等待设置显式超时;否则该预设的默认值会分别把某个步骤留住 60 秒和 30 秒。宿主通过 presetForSession 确定当前预设,退化到 Session 投影(projection)或头部(header),若读不到该值则假定其为 standard。

执行器工具

每个执行器有六个应用工具:

工具
用途
orchestrator_read
读取其自身工单可用的资源
orchestrator_ack
确认一个请求
orchestrator_report
报告进度或结局,并按 pull request 规则绑定某个 pull request
orchestrator_thread_unrelated
丢弃一个无关的附加线程,交还它所拥有的追问,并告知主 Session 原因
orchestrator_thread_follow
仅为上下文订阅一个相关的线程,并告知主 Session
orchestrator_trigger_handback
把一个消息交还一次,附带其候选所携带的理由

若另一个工单仍拥有该消息,或本工单从未拥有过它,则交还返回 queued: false。例如,一条 bot 消息独立地进入了评估。

worker 预设定义在 cordis.patch.yml 中,并通过 WORKER_PRESET 选定。src/app/sessions.ts 中的 WORK_TOOLS 保留可用的文件、搜索、shell、任务、web、技能、待办和子代理工具。worker 不会收到用于向人类提问或呈报、更改配置文件、在 worker 槽位之外调度工作、启动其他 Session 或向其发消息、或编辑外部文档的工具。在工单结束之后就拒绝工具调用。

结果形态与评估上下文

路由结果使用 {decision?, reason, ticket?}。orchestrator_route 返回 {results},保持条目顺序,并为每一项加上其 trigger_id 与 kind。无效或被拒绝的项返回 refused 和一个理由,而不中断整批。

设置与提示词保存返回被保存的配置。策略同步返回该来源、允许列表的大小,以及 added 和 removed 名称。

orchestrator_eval 返回的是上下文而非指令。评估指令位于 runtime/admission(prompts/admission.md),规则位于 runtime/policy。其上下文包括:

  • 触发文本,最多 4,000 字符,连同图片 id 和批次中的其他触发。
  • 最多八个 related_tickets,处于打开状态的排在前面,以及 tickets_total。
  • 该评估的 policy_version。
  • 一个线程窗口:前五条消息,以及触发两侧各五条。消息使用 {id, from, at, text},通常限 400 字符,两侧那两条则为 1,200 字符。被截断的消息带有 chars;图片引用使用 images。
  • 其他被载入的消息以 50 字符的 {id, from, preview} 存根形式呈现。orchestrator_eval_read 按 id 将其展开。

工单卡片使用 src/app/cards.ts 中封闭的 TICKET_STATES 集合:一个处于打开状态的阶段,例如 person/review 或 working/queued,或一个已结束的结局。无论经 accept、评估还是 orchestrator_ack 提交,确认都限于 100 词。

读取资源

src/app/resources.ts 中的 RESOURCES 定义各资源,并生成该工具的 resource 描述。

资源
内容
tickets
、triggers、threads、decisions
分页列表,形如 {items, next_offset};活动列表把最新的排在前面
decision/<id>
一个决定
thread/<key>
标签、工单、近期的决定以及一页触发
trigger/<id>
消息文本、被回复的触发及相邻消息,不含 CI 运行
ticket/<id>
相关 id、来源批次的触发、线程,以及派工和输入的首页
工单子资源
/assignments
、/inputs、/assignment、/result、/report/<assignment id>、/transcript、/message/<n>、/input/<id>
settings
、settings/<block>
修订版本与摘要,或一个可直接保存的完整块
prompts
、prompts/<revision>、prompts/current、prompts/defaults、prompt-preview/<id>
提示词配置与合成后的文本
models
、models/<provider>
可用的模型
feishu/<trigger id>/…
、github/…
来源原件记录,其说明见来源技能

工单的线程会针对仅为上下文的跟随标记为 context,针对来源绑定标记为 bound。派工的页面把最新的排在前面,并包含报告的预览。用 next_offset 续读列表。/input/<id> 接受一个输入的名称或 id。settings 完整包含较小的块,并对策略与来源块给出摘要。

来源资源包括消息邻域、github/pulls/<n>/thread/<comment id> 和 github/commits/<sha>/checks。

一个原件的消息以一种紧凑形态读取,形如 {id, from, at, text, reply_to?, path?, line?, url?},而非按 GitHub 或飞书记录它们的原样。

某个执行器 Session 只能读取其自身工单、其线程、曾抵达它的或其所持请求包含的触发、它工作于其下的提示词版本及原件;其页面会省略它无法读取之物的 id、决定以及其他工单。

某页面所显示的每一个 id 都能通过上述之一读取,于是 Session 能从一个对象走到下一个对象;没有任何东西以之为据来读取的哈希,例如评估的 id 或策略版本,都不会出现在页面上。

一个文本页面默认为 4,000 个 UTF-16 码元,最多 8,000 个;一个列表默认为 20 项,最多 50 项。

较早版本中的 event/、task/ 和 observation/ 这些名称仍可解析。

投递给 Session 的消息

以下消息会被引导进主 Session 的一轮:

消息
内容
候选
批次的触发卡片、线程、related_tickets、tickets_total、delivered_to、由 Issue/PR 链接引用的工单、handed_back 理由,以及下一步动作
follow_up
一条消息,以其执行器的视角呈现、replies_to、接收方工单,外加提醒:由执行器来决定它,而主 Session 只能停止工作
worker_report
工单卡片、来源触发、状态,以及最多 600 字符的摘要;被截断时带有 chars 和一个读取指针
worker_attention
承载该提问的工单卡片

其他通知则在不唤醒主 Session 的前提下被注入:ticket_ended、settings_saved、提示词保存、触发重新排队、被丢弃的订阅以及上下文跟随。一条设置通知会点名该块、发生变更的路径,以及 read: settings/<block>。对于主 Session 自己执行的保存、重新排队、退订或附加,它不会收到多余的通知。

worker 以内联方式接收派工和输入。第一条消息包含标题、最多 1,500 字符的派工文本,以及来源触发卡片。输入携带一张最多 600 字符、并带 replies_to 的触发卡片,或纯输入文本。读取指针仅针对被截断的内容出现。

仅面板可执行的操作

没有哪个工具可用于:驳回一个 unavailable 的决定、重新运行 setup、刷新来源显示名称,或把一个由执行器拥有的人类追问发往评估。最后这一操作被 orchestrator_trigger_requeue 刻意拒绝。其余面板操作都有等价的工具。

大多数内置指令位于 prompts/*.md 中,一句一行,由 promptFile 拼接。来源技能位于 skills/dsh-superagent-*/SKILL.md。工具描述以及诸如 restart 恢复备注之类的固定通知仍留在代码中。评估指令仅位于 prompts/admission.md。

tests/model-surface.spec.ts 会检查这些接口面是否发生漂移。004 提示词 解释了团队可编辑的部分。

配置重载

除 stateDir 之外的每一个插件配置键都是易变的。面板的 Deployment 设置与 DeepSeek Harness 的插件管理器,通过 DeepSeek Harness 的配置表单来编辑它们;宿主写入 cordis.patch.yml 并就地更新这些取值,而 dsh-superagent 在每次用到某个限额时才读取它,因此变更无需重启即可生效。

一次更改了 setup 所读取的某个来源字段的设置保存(例如某账号的 CLI),以及在 setup 处于等待期间的任何保存,都会重启 dsh-superagent。在此期间,DeepSeek Harness 的设置服务不会提供该条目,所以在其自身重启之后,dsh-superagent 会要求再次读取该条目,一个已打开的表单便重新可编辑。

用真实数据复核改动

把 DSH_HOME 指向一个临时目录,并在其中新建一个 profile。从一个备份复制数据库,禁用每一个来源,暂停处理,并移除主 Session id。为 GitHub 与飞书 CLI 提供只读的替身(stand-in)。

悬而未决的问题

  • 信任:哪些触发字段是可信的,例如该来源的 actor 与线程成员关系;哪些不可信,例如正文与引用。
  • 部署隔离:agent 的 profile 位于其自身固定的 DeepSeek Harness 安装之上,从而在其他地方升级 DeepSeek Harness 不会破坏它。

回首页,搜索你想要的文档

在首页搜索框输入关键词,快速定位资料

文库首页搜索指引 立即搜索

相关学习资料