这是实现的开发者指南。它把 001 领域模型到 006 部署中的概念映射到类、接口与文件。先读运行时与 Engine,再把模块表和测试表当作代码索引使用。
核心类与胶水层
核心类在小型接口背后拥有领域概念与外部边界。胶水层把工具参数、HTTP 请求、Source 载荷与 DeepSeek Harness 事件翻译成对这些类的调用,再把结果转成卡片、视图与消息。
Runtime(src/app/runtime.ts)构造应用、提供依赖,并拥有启动与关闭。Engine 构造自己的内部部件。Executor 与 Assessor 会回调 Engine,因此 Runtime 交给它们的是 getter,而不是要求按循环顺序构造。
Runtime
Runtime 是组合根(composition root);它不含领域规则。其构造函数锁定状态目录、打开 Store 并构建各组件。构造失败时,它会关闭 Store 并释放锁。
启动时注册管理路由并运行 Setup。setup 解析完成后,它安装 Session 与 Executor 钩子、确保主 Session 存在、启动 Policy 同步、恢复被中断的 Assignment(派工),并启动 passes 与 effects。ready 标记启动完成。
关闭时取消回合、等待 passes、effects 与 agent、恢复进程环境、关闭 Store 并释放锁。Source 插件通过狭窄的 SourceHost 接口把 Runtime 注入为 orchestrator 服务。
| 部分 | 模块 | 职责 |
|---|---|---|
| Setup | src/app/setup.ts | 按 worker shell 的运行方式探测仓库与各账号的 CLI 来解析它们,写入并应用 worker 的环境与命令 shim,保存 setup 问题;Settings 经它检查变更的账号,重启也经它进行(即再次运行 setup) |
| Passes | src/app/passes.ts | 每两秒一次、且每当有命令请求时运行一个 pass:先处理到期的 effect,再运行 Engine.evaluateDue;Source 的 Trigger 经它到达 Engine |
| Outbox | src/app/outbox.ts | Engine 的 Outbox 端口,也是回执的唯一写入者:send 在一个事务里保存消息回执并排队其 reply effect,effect 的处理程序经 SourceRegistry.send 发送;Source 关闭期间消息等待,会话消失或不再被监听时消息被丢弃 |
| Notices | src/app/notices.ts | 把领域事件与 worker 活动转换为主 Session 消息。eventNotice 处理领域通知;reportRelay 立即转发问题、把普通进展挂在它的 Ticket 上、转发其余报告;followupRelay 点名拥有人后续消息的 Ticket。回合期间到达的转发在其下一步经 DeepSeek Harness 收件箱一并投递 |
Engine
Engine(src/core/engine.ts)是 Trigger(触发)、Thread(线程)、Decision(决定)、Ticket(工单)与 Assignment 的唯一写入者。它从 src/core/ports.ts 接收这些端口:Repositories、当前 Policy getter、SourceScope、Assessor、Executor 与 Outbox。可选依赖为 prepareTicket、now 与 emit。
所有 Engine 方法都是同步的。每条命令在一个事务里改变其聚合。evaluateDue 为它改变的每个 Eval 或 Ticket 使用独立事务。端口在事务内排队外部工作;适配器在提交之后执行。无效请求抛出 UserError。
Engine 暴露一个入口,并委托给五个内部部件。它拥有每条命令的事务。跨越多个部件的命令(如 attach、evaluateDue 与 policyChanged)按顺序调用各部件。
每个部件扩展 EnginePart(src/core/part.ts),后者提供选项与共享读取。部件间的依赖单向流动:
| 部件 | 职责 | 依赖 |
|---|---|---|
Intake | Source 的 Trigger:连同其去向存储一次、记录到其 Thread 与关联上、扇出到订阅的 Ticket;除非它是到达某个 Ticket 的人的消息,否则在其主 Thread 上排队等待 Eval | Tickets |
Evals | Thread 的 Eval:分批、gate 的强度/actor/依据与给出它们的 Trigger(primary)、候选送达起算的时限及其因暂停的顺延、过期 Eval 的重新排队、候选、重放与交还、Policy 变更时的重新排队,以及每个 Eval 结束时的 Decision:accept、stop、ignore 或 unavailable | Tickets |
Tickets | Ticket 生命周期:从 accept 创建(含 Mandate、Subscription 与第一个 Assignment)、它跟随的 Thread 及其关闭(followsDone)、附加的 Thread 与仅为上下文跟随的 Thread、Input 与备注、恢复及恢复所拒绝的消息、重试、重启、stop 与结束、它丢弃的 Subscription,以及 Policy 变更对其波及 Ticket 的通知 | Replies |
Assignments | Executor 及其 worker 报告的事实:Assignment 启动、Input 到达它或送达失败、重启中断了它、worker 的 Report 及其给人的问题 | Tickets、Replies |
Replies | 经 Outbox 的 Channel 写入:确认、回答、结果与 attention 消息,各自依允许它的规则发出 | 无 |
命令使用祈使式命名(如 stopTicket);适配器报告的事实使用过去式命名(如 assignmentStarted)。查询 candidate 使用名词。
调用者只收到其角色的 Engine Pick:EngineForSources、EngineForMainSession、EngineForPeople、EngineForSettings 或 EngineForExecutor。
| 角色 | 方法 | 效果 |
|---|---|---|
| Source | receive(trigger) | 存储一次 Trigger,应答 stored、duplicate,或无 Source 接收时 filtered;扇出到订阅其 Thread 的 Ticket——人的消息在此即刻恢复已结束的 Ticket(除非处理已暂停)——并在其主 Thread 上排队等待 Eval;若它是已到达某个 Ticket 的人的消息,则留在该 Ticket |
| Source | bindThread(ticketId, thread, own) | 让一个开放的 Ticket 跟随 Source 的某个 Thread(例如 pull request 的);Ticket 开启它时 own 为真。已关闭的 Thread 被再次绑定则重新开放。Thread 的关闭(而非 ready Report)结束 Ticket |
| Source | threadClosed(thread, outcome, at) | 一个 Thread 关闭:跟随它的每个开放 Ticket 记录该关闭,并在处理完它跟随的 Thread 后(followsDone)结束:它开启的 Thread 都不再开放,或(未开启过时)它跟随的 Thread 全部关闭。它按其中最近一次关闭的结果以 merged 或 closed 结束——除非关闭后恢复的工作尚未报告 ready,或其 Executor 在其最新非 progress Report 或最后回合结束之后,以确认或回答的方式接手了它要裁决的人的后续消息;此时一条 dropped Report 会让它按该关闭的结果结束 |
| 主 Session | candidate(triggerId) | 持有某 Trigger 的 Eval,按其研判者所见 |
| 主 Session | candidateDelivered(evalId) | Eval 的候选到达主 Session:其时限从现在起算 |
| 主 Session | decide(triggerId, outcome) | 记录 Eval 的 Decision,或对 explicit 请求的直接 Decision。accept 原子地创建 Ticket、Mandate、Assignment、Subscription 与确认。从卡片直接忽略 inferred 候选时,在给定理由之前先记录 ignoredFromCard。followupOwners 仍拥有某条人的后续消息期间,只允许 stop |
| 主 Session | attach(ticketId, triggerId, note?) | 把 Trigger 路由到它指向的 Ticket,其 Executor 随后将其作为后续消息裁决,并结束持有它的 Eval——该 Eval 的其余 Trigger 等待新的 Eval;除非处理已暂停,已结束的 Ticket 在其 Mandate 下即刻恢复。Executor 仍在裁决的后续消息被拒绝;被路由回交还它的那个 Ticket 的后续消息同样被拒绝 |
| 主 Session | requestStatus(ticketId) | 向等待中的 Executor Session 询问状态,并应答它是否问了 |
| 主 Session | note(ticketId, text) | 把其他工作的事实作为文本 Input 交给 Ticket 的 Executor;不授予任何东西 |
| 主 Session、Executor | reply(request) | 为其用途所指的 Channel 排队一条消息:acknowledgement、answer、result 或 attention |
| 人 | stopTicket(ticketId, reason?) | 人的 stop,撤回 Mandate |
| 人 | retryTicket(ticketId) | 结束 failed / blocked Report 后等待人的 Ticket、或 Input 送达失败的 Ticket(canRetry)的停滞 Assignment,并启动新的 |
| 人 | reevaluate(triggerId) | 把一个已存储的 Trigger 排队等待新的 Eval |
| 人 | unsubscribe(thread) | 丢弃某 Thread 上的全部 Subscription;跟随它的 Ticket 不再跟随,各自继续裁决已收到的后续消息 |
| 人 | dismiss(decisionId) | 消除一条 unavailable Decision 显示的问题,并由该 Decision 记录 |
| Executor | assignmentStarted(ticketId, assignmentId) | 一个 Assignment 在运行;其等待中的 Input 发出 |
| Executor | inputsDelivered(ticketId, assignmentId, inputIds) | Input 到达 Assignment 的 Executor Session,各自等待某个步骤取走 |
| Executor | inputsRead(ticketId, inputIds) | Executor Session 把携带这些 Input 的消息记入其历史(一次 user/message Session 事件),其模型由此可见;认领后被取消或失败于记入之前的步骤不算。一旦随后有 Report 或回合结束即算已读(重启后亦然),Ticket 的结束不改变这一点;在记录已读之前送达的 Input,凡送达后跟有 Report 或回合结束即算已读 |
| Executor | turnEnded(ticketId, assignmentId) | Executor Session 的一个回合结束——也在 Report 之后、当后台无任何为其运行之物或它向人移交之时。步骤此前取走的即已读(inputsRead) |
| Executor | deliveryFailed(ticketId, assignmentId, error) | Input 送达失败;Ticket 显示该状态直到送达成功、新 Assignment 启动或 Ticket 结束 |
| Executor | report(ticketId, report) | 记录一条 Report 及其给人的问题;dropped 结束 Ticket,ready 完成不跟随任何 Thread 的 Ticket,并对处理完所跟随 Thread 的 Ticket 按那些 Thread 的结束而结束 |
| Executor | markUnrelated(ticketId, thread, reason) | 仅丢弃该 Ticket 对它认定无关的附加 Thread 的 Subscription,并连同理由交还该 Ticket 在那里裁决的后续消息 |
| Executor | followForContext(ticketId, thread) | 为获取上下文让开放 Ticket 订阅相关 Thread:其消息到达 Ticket,但不借予后续强度,也不恢复它 |
| Executor | handback(ticketId, triggerId, reason) | 记录该 Ticket 交还一条到达它的 Trigger,理由随其候选携带;若确由此 Ticket 裁决,则在无其他 Ticket 裁决时排队等待新的 Eval;应答是否已排队。Ticket 对每条 Trigger 只交还一次并保留其 Subscription |
| Executor | assignmentInterrupted(ticketId, assignmentId) | Assignment 运行中宿主停止:忙碌中被中断的、或在其最新 Report 后收到过 Input 的,带恢复备注及其 Executor Session 未取走的已送达 Input 重启——与重试相同;此后有报告的保留其 Session。DshExecutor 在启动时逐个报告(resume),最早的 Ticket 优先,并把等待中的启动重新排队 |
| Passes | evaluateDue() | 除非处理已暂停:启动排队 Trigger 正在等待的各 Thread 的 Eval——每 Thread 一个,期间排队的 Trigger 合为一批;把超过时限的 Eval 的 Trigger 重新排队,连续两次,第三次记录 unavailable;并恢复暂停期间人的消息到达的已结束 Ticket——若其 Mandate 仍然成立。候选尚未到达主 Session 的 Eval 不过期。处理暂停期间无 Eval 过期;恢复后各时限顺延暂停时长 |
| Settings | policyChanged(from, to, effects) | 告知每个受影响的活跃 Ticket 变化如何波及它(src/policy.ts 的 policyEffects:它被接受所依据的规则变更或不再适用、请求者可能不再能发起工作、或不再接受主动工作),并在新版本下重启进行中的 Eval |
Engine 把分批、授权、扇出、恢复与 attention 生命周期规则保持为私有。若干共享谓词让这些规则保持一致:
waitsForPerson(src/core/model.ts):最新 Assignment 报告了blocked或failed,且此后无人消息到达。该状态持续期间,自动 Input 与失败启动保持挂起。followupOwners:仍对自己 Thread 上收到的人的后续消息负责的 Ticket。Ticket 可以是活跃的,也可以是未被人 stop、等待恢复的已结束 Ticket。交还该消息即结束此归属。settled:一个 Decision 已解决该请求;stop、unavailable、routed 与 refused-stop 的 Decision 不算。ticketStage(src/core/stage.ts):实现 003 执行中的阶段表。
Ticket 结束时,未被其他 Ticket 拥有、也未被其他去向解决的未读后续消息回到 Eval。已确认、已回答、已交还的消息除外。对没有确认去向的旧记录,由 outbox 回执补充该事实。最终的 Report 也覆盖 Assignment 开始时等待中的消息,包括恢复它的那条消息。停止 Ticket 的那条消息不会被自动重新评估。
变更之后,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 转为通知。送达 Ticket 的人的 trigger.received 成为后续转发。Ticket 以 merged 或 closed 结束时,pullEndNotices 以稳定 id 发出一条聊天通知。其余事件暂无消费者。
Model
src/core/model.ts 保存领域类型,src/core/stage.ts 保存 Ticket 阶段。Decision 用 reason 记录原因——一组 Reason(src/core/reasons.ts):各含一个 code 及其 values,批次中某个 Trigger 的还带其 triggerId。
核心的代码(如 timeout、policyChanged、routed、startedTicket)构成 REASONS,附 agent 经 reasonText 读取的英文;gate 或 Source 范围给出自己的(如 not_a_member);said 携带研判者或人写下的文字。面板以 reason.<code> 为每个代码配词;被 Source 过滤的 Trigger 以同样方式记录该 Source 的代码。
Ticket 的每个事实只保存一次。没有 ended 时它是开放的——ended 记录何时、由谁、为何、以何种结果结束;它最后说的话是其最新 Assignment Report(latestReport);其 Executor Session 以它命名(src/executor/dsh.ts 的 workerSessionId)。
其字段有类型:origin,即可准入的那个 Trigger——它给出 Eval 批次的强度——以及批次本身(triggerId、triggerIds);execution,Executor 的选择(name、directory 与 assignment);delivery,最近一次失败的 Input 送达(error、at),直到送达成功、新 Assignment 启动或 Ticket 结束;follows,Source 让它跟随的 Thread,各为 {thread, own, closed?}:Ticket 开启的 own 为真,关闭后带 closed(含 at 与 outcome)。只有 Tickets 写 follows;GitHub Source 用 pullsOf 与 pullOf(src/sources/github-pull.ts)读取其 pull request。
携带 Trigger 的 Input 记录是否人所写,供 waitsForPerson 读取。Thread 还在 context 中列出仅因上下文跟随它的订阅 Ticket,并在 expired 中累计其排队 Trigger 连续过期的 Eval 次数;Evaluation 记录其 primary Trigger 及候选何时 delivered。
Trigger 的去向记录它到达的 Ticket(经扇出或附加)、人的重新排队、每个交还它的 Ticket 及理由,以及其 Executor 确认或回答过它的每个 Ticket。
Mandate 的 basis(MandateBasis)记录 gate 如何准入其 Trigger。重新校验读取接收范围 source、scope、row、列表中的 sender、匹配的 rules 与 via:
via | 准入依据 |
|---|---|
addressed | 允许的发送者指向了 agent |
followup | 消息到达了有 Ticket 的 Thread |
admission | 准入表的某格;observation 标记 inferred 候选 |
bot | 列表中的 bot 产生了 inferred 候选 |
Trigger 与 Thread 的 meta 属于 Source。核心不解释它们。
Policy
src/policy.ts 定义设置 schema、授权与研判规则。settingsPolicy(settings, scope) 每次使用都返回一个新的无状态 Policy:
version、paused与evalTimeoutMs描述其当前状态。gate(trigger, thread, tickets)返回准入强度与依据。revalidate(mandate)检查既有 Mandate 是否仍然成立。admitsInferred(rules)只在 auto 模式且有启用的匹配规则时允许主动接受。
assessmentOutcome(assessment, settings, strength) 把模型的研判转换为 Engine 的结果。它要求对每条启用的规则都有判定、方向已定、有匹配规则与意图证据。非 auto 模式下,inferred 的 accept 变为一条「would accept」记录。
policyEffects(tickets, old, now, scope) 识别一次变更波及哪些开放 Ticket、如何波及。Engine.policyChanged 只通知这些 Ticket。policyVersion 对规则、accept 模式与各 Source 的 Policy 部分(含 bot 列表)做哈希。
Store
Store(src/app/store.ts)拥有一个 SQLite 数据库。Runtime 打开它之前先取得目录锁,防止第二个进程迁移或写入同一状态。
其接口按用途分组:
- 领域仓储:
atomic,加triggers、threads、tickets、decisions,各有get、save与列表查询。索引化的工作集为threads.active()、tickets.open()、tickets.active()与read.subscribedThreads。 - 读取查询:
read经threadTriggers、triggerStats、search、droppedReplies等方法服务视图、工具与 Source 轮询。这些方法从不写入。 - Source 元数据:
threads.describe与threads.patchMeta更新 Source 拥有的 Thread 元数据。 - 设置:
settings、saveSettings、blockRevision与settingsHistory。设置与 prompt 使用编号的Versions(src/app/versions.ts)。 - Outbox 回执:
replyStatus与receipts,仅由 Outbox 写入。回执操作为get、waiting、behind、add、settle与withdraw。behind检查同一 Ticket 发往同一会话的较早消息是否在等待且失败少于三次。被撤回的消息以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 注册一个处理程序。它按从旧到新处理到期行,上限为并发限制。
失败按指数退避重试。抛出 Later 在不计失败的情况下推迟工作——例如没有空闲 worker 槽位时。start 在 setup 之后开始处理;drain 在关闭期间停止处理并保留待处理行;pending / idle 暴露队列状态。
| Kind | 处理程序 | 做什么 | 并发 | 重试 |
|---|---|---|---|---|
assessment | MasterAssessor | 把 Eval 的候选卡片引入主 Session 的回合,然后告知 Engine 已送达——由此启动该 Eval 的时限 | 4;暂停期间为 0 | 2 秒起倍增至 60 秒 |
launch | DshExecutor | 启动 pending 的 Assignment:prepare 命令、Executor Session、assignment 消息 | 空闲 worker 槽位数;暂停期间为 0 | 模型供应商或网络的临时性失败以 5 秒起倍增至 300 秒重试三次;其他任何失败、以及 prepare 命令失败,使该 Ticket 失败。被暂停或槽位占满所阻的以 5 秒后再试 |
delivery | DshExecutor | 按顺序把 Ticket 等待中的 Input 交给其运行中的 Assignment | 4;暂停期间为 0 | 2 秒起倍增至 60 秒;等待 worker 槽位的以 2 秒后再试 |
relay | Sessions | 把 worker 的报告或问题、或人的后续消息引入主 Session 的回合 | 1,按队列顺序 | 2 秒起倍增至 60 秒 |
notice | Sessions | 向主 Session 注入通知而不唤醒它 | 1,按队列顺序 | 2 秒起倍增至 60 秒 |
reply | Outbox | 在其 Ticket 发往该会话、未连续失败三次的较早消息之后,经 Source 为 Channel 发送消息;撤回该消息即取消 | 1;暂停期间为 0 | 5 秒起倍增至 300 秒;Source 关闭或被较早消息挡住的以 5 秒后再试 |
每个 kind 以其名称有一条健康项。同 kind 的一条 effect 连续失败三次后报告错误——delivery 例外,其失败显示在所属 Ticket 上。转发保持顺序,通知亦然;一条失败挡住它同类的后续条目。
Source
Source 经 src/sources/kind.ts 定义的 SourceKind 扩展宿主。一个 kind 声明其身份、标题、描述、配置 schema 与 fields、派生 facts 与 health 项。可选行为包括 scope、channel(allowed、send、link)、threadContext、find、policy、environment 与 refreshLabels。
SourceRegistry(registry.ts)拥有已注册的 kind 与已存储的实例,并实现 Engine 的 SourceScope。注册在 HealthBoard 上认领健康项;重复认领与保留的 sync: 名称被拒绝。SourceContext 只经其注册认领的项写入,因此注销之后的报告不做任何事。
Registry 的配置方法包括 kind、instances、instance、validate、validatePolicy、policyPart、channelAllowed、declarations、environments、find 与 scope。find 询问每个支持搜索的 kind。
通用代码经 registry 访问 Source 行为。send(message, commands) 调用该 kind 的 Channel,kind 未安装时返回 sent、gone 或 waiting。link 解析其 Channel 链接。threadContext 把 kind 的读取器与其配置配对,kind 无读取器时返回空。无效的已存储配置会禁用该实例。
插件收到 SourceHost:registry 与 context(kind)。kind 本身使用更窄的 SourceContext,由 src/app/source-context.ts 的 sourceContextOf 构建。
该上下文提供部署与命令、setup 就绪态(ready、configured、problem)、配置、receive、atomic 与类型化 state。它还暴露 Thread 读取与元数据写入、Ticket 读取与绑定/关闭命令、Trigger 读取、经 Outbox 的 notify,以及为声明的健康项准备的 report。
DshExecutor
DshExecutor(src/executor/dsh.ts)为每个 Ticket 提供一个 Session 与一个目录。start 与 deliver 排队 launch 与 delivery effect。stop 经 Store.afterCommit 取消回合,因此回滚的结束不会停止 worker。
prepare(ticket, outcome) 提供 Engine 的 prepareTicket 钩子:短名、目录与 ticketExecution 的 assignment 文本。workerSessionId(ticket.id) 导出 Session id。
WorkerSlots 在启动前、或唤醒空闲 worker 的送达前预留容量。人的消息可使用超出 maxWorkers 的一个槽位,且唤醒优先于启动。activeCount 提供面板负载。
临时性的供应商与网络失败获得三次重试。启动使用 effect 退避;失败的回合在 15、30、60 秒后收到继续消息。inputMessage 从当前 Ticket 状态渲染每个 Input,mandateInstructions 为 inferred 工作追加匹配规则的限制。
install 订阅 Session 事件;dispose 退订。回合结束通常报告等待——就绪之后或需要人行动时除外。错误报告为失败。启动时,resume 经 assignmentInterrupted 报告被中断的 Assignment,并重新排队不等待人的 pending 启动,最早的 Ticket 优先。
MasterAssessor
MasterAssessor(src/app/assessment.ts)让主 Session 研判主动候选。request 排队一个 assessment effect;release 在其 Eval 结束时丢弃缓存状态。
其 context、read 与 decide 方法服务 orchestrator_eval、orchestrator_eval_read 与 orchestrator_eval_decide。它们经 SourceRegistry.threadContext 提供有界的 Thread 上下文、仓库上下文与决定提交。没有上下文读取器的 kind 只提供 Trigger 与批次。
assessmentOutcome 为 Engine.decide 转换模型的回答。Evals 会对 inferred 接受再对照当前 Policy 检查一次。
Settings
Settings(src/app/settings.ts)读写带 revision 的设置块。current 与 view 读取;save(block, value, revision, origin) 校验保存;pause(paused, origin) 更改单独的 paused 设置。
每次保存声明其来源:panel、tool 或 sync。只有 panel 能更改执行环境或 SETUP_FIELDS(src/deployment.ts)列出的字段。
Policy 版本变更会计算 policyEffects、记录哪些 Ticket 被通知,并调用 Engine.policyChanged。syncable 与 sync(id) 刷新 Source 的 Policy 部分。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,丢弃已存数据中已移除的部分,拒绝空白文本与其他未知键。这使保存工具能完整往返它读到的一份配置。environmentSection 与 policySection 用各自的键一次性代入模板值。无覆盖的配置使用默认。
Sessions
Sessions(src/app/sessions.ts)为两种角色管理 DeepSeek Harness Session。ensure 以其角色的模型创建 Session。install 跟随 agent 事件并为受管 Session 注册 prompt、技能与工具。
prompt 每个请求 id 只投递一次。relay 与 notify 排队由 relay 与 notice effect 处理的主 Session 消息。src/app/notices.ts 定义其内容;delivery 检查消息是否到达。
每次模型请求把 Session 绑定到当前 prompt revision。它用过的 revision 被持久保存:promptVersion 标识活跃的那个,mayRead 限制 worker 能查看哪些版本。注册情况按 agent 各记在一张映射里。
status 与 promptVersion 服务 prompt 页面。保存经 Prompts.save 并通知主 Session。modelRetry 暴露模型预算重试状态。
胶水层
| 胶水 | 翻译什么 |
|---|---|
src/app/runtime.ts | 组合根:以各部件使用的接口构建它们、注册路由与 Source 宿主,并启停 dsh-superagent |
src/app/notices.ts | 领域事件与 worker 报告,为主 Session 生成通知与转发 |
src/app/tools/main.ts、src/app/tools/executor.ts | 主 Session 与 Executor 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 | 资源名转为读取;Source 消息转为链接;记录转为给模型的紧凑卡片 |
src/sources/github*.ts、src/sources/feishu*.ts、src/sources/poll.ts | GitHub 与飞书载荷转为 Trigger 与 Thread 事实;消息转为 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 只在这样的接口背后传递自身。Source 插件注入orchestrator服务,其类型名为SourceHost:registry 与context(kind);其 kind 的代码只见自己的SourceContext。 - 谁可以 import 谁。
src/core除 Node 的哈希外不 import 任何外部内容,也不做 I/O。src/sources不从src/appimport 任何东西。面板只从宿主侧 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的过滤列表。 - 每种状态一个写入者。每类状态只有一个写入者,其余读取它或询问它。
Source 只经状态 写入者 Trigger、Thread、Decision、Ticket、Assignment Engine Source 的读取位置,及其在 Thread 上保留的 meta( label、url、facts及自有键)该 Source,经 SourceContext设置及其历史 Settings Prompt 版本 Prompts Effect 行 Effects Outbox 回执 Outbox:Engine 的消息与 Source 的 notify经其send发出,其replyeffect 标记它们健康项 在 HealthBoard(src/infra/health.ts)上认领它的那个部分,第二个认领被拒:runtime为 Runtime(Passes 经它报告)、evals为每遍后的 Runtime、models为 Sessions、各 effect kind 为 Effects、worker为 DshExecutor、Source kind 的各项为该 kind Source 的 SourceRegistry、sync:<id>为 SettingsSourceContext.tickets.bindThread与threadClosed更改 Ticket——它们调用 Engine。 - 请求与事实。请求某物的方法以祈使动词加宾语命名(如
stopTicket、bindThread),可被拒绝;记录适配器所做之事的方法以过去式命名(如assignmentStarted、deliveryFailed)。每个宿主收到它所需角色类型的 Engine。 - 字段有类型,Source 数据归 Source。代码读取 Ticket 与 Mandate 的类型化字段。Source 不在 Ticket 上保存任何东西:Ticket 跟随它哪些 Thread 记在 Ticket 的
follows;它对 Trigger 或 Thread 的了解在其meta里。 - 固定的拒绝。人或 agent 可触发的拒绝是
src/core/errors.ts中USER_ERRORS的UserError(code, values)。管理 API 以 404 应答missing代码、其余以 400;面板以自己的语言为每个代码配词。setup 问题为SetupError代码(src/setup.ts),固定健康文本为HealthCode(src/sources/host.ts),措辞方式相同。
Source 专属依赖
部分通用模块仍直接引用 GitHub 或飞书:
- Outbox 应用飞书的会话字数上限。
src/app/links.ts解析飞书消息链接与repliedTo。它与 Runtime 用feishuSettings读设置。src/app/resources.ts读取feishu/与github/原件;src/app/cards.ts在 Ticket 卡片用pullOf。- Runtime 为 merged 或 closed 的 Ticket 发送
pullEndNotices。 - Executor 工具用
readPull与claimPull绑定被报告的 pull request,使用 Runtime 暴露的 GitHub 上下文。 - MasterAssessor 用
readRepositoryPath读取仓库文件。 - Sessions 与 Prompts 仅为 GitHub 和飞书注册内置技能。迁移转换其 schema-10 记录。
- Setup 只探测 GitHub 与飞书的账号字段(
SETUP_FIELDS)。只有这些 Source 经SourceContext.problem收到账号 setup 问题。
模块
| 路径 | 职责 |
|---|---|
src/index.ts | 插件主入口:读取机器设置、启动 Runtime,并把 Source 账号保存所用的重启交给它 |
src/deployment.ts | 插件 Config 与 Machine;resolveDeployment 与 resolveFeishuBot 从 Source 设置读取仓库与各账号,每个 CLI 按 Executor shell 的方式运行;SETUP_FIELDS 列出 setup 读取的 Source 字段。代码不出现任何人名、账号或组织 |
src/setup.ts | setup 问题:SetupError,含代码、修复位置,以及日志、主 Session 与面板共用的英文文本 |
src/policy.ts | Policy:按块的设置 schema、Policy 版本、gate 与 Mandate 重新校验 |
src/admission.ts | 准入表的取值、每格的选项与默认,与面板共享 |
src/limits.ts | 人所设值的上限;设置 schema 校验它们,面板字段提供它们 |
src/kv.ts | KV:每族 kv 记录的键、值类型与拥有者,及 Store 与 SourceContext.state 所用的 Key 与 Family 类型 |
src/navigation.ts | 宿主目录列出后,从面板打开 DeepSeek Harness Session |
src/core/model.ts | 领域类型,含类型化的 Ticket 与 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.ts | EnginePart,Engine 各部件的基类,及 EngineOptions |
src/core/intake.ts | Intake:来自 Source 的 Trigger,到其 Ticket;除非人的消息已到达某个 Ticket,否则到 Eval |
src/core/evals.ts | Evals:Thread 的 Eval 及其 Decision |
src/core/tickets.ts | Tickets:Ticket 生命周期、Ticket 跟随的 Thread、Subscription 与 Input |
src/core/assignments.ts | Assignments:Executor 的事实及其 worker 的 Report |
src/core/replies.ts | Replies:Engine 的 Channel 写入 |
src/core/stage.ts | ticketStage 与 canRetry:003 执行的 Ticket 阶段 |
src/core/errors.ts | UserError 与 USER_ERRORS |
src/core/reasons.ts | Reason、REASONS 与 reasonText:核心为何记录 Decision 或搁置 Trigger,按代码 |
src/core/ids.ts | key:由输入导出的 id,相同输入即相同 |
src/core/memory.ts | MemoryRepositories:Store 的仓储之于内存,顺序与嵌套回滚与 Store 相同,用于领域测试 |
src/core/index.ts | 核心的导出 |
src/app/runtime.ts | Runtime,组合根,及其启停 |
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.ts | storeReads:Store 服务视图、工具与 Source 轮询的读取查询,从不写入 |
src/app/migration.ts | schema 11 迁移:从 schema 10 一步把每条已存记录写为其当前形态 |
src/app/effects.ts | Effects |
src/app/versions.ts | Versions:以编号快照保存、指向当前项的记录,用于设置与 prompt |
src/app/cache.ts | TextCache:限量内存中的文本,最近最少使用先淘汰 |
src/app/settings.ts | Settings |
src/app/settings-history.ts | policyHistory:每次保存的设置 revision 及相对前一次的变更 |
src/app/prompts.ts | Prompts、builtInParts(团队可覆盖的内置部分)与 promptParts(角色 prompt 段落的顺序) |
src/app/context.ts | 内置 prompt 文本(读自 prompts/)与 Session 读取的会话条目 |
src/app/skill-files.ts | 打包文本:promptFile(把文件的单句行合成段落)与随包技能 |
src/app/sessions.ts | Sessions |
src/app/delivery.ts | AgentDelivery:按请求 id 向 DeepSeek Harness agent 投递一次消息,恢复的 Session 亦然 |
src/app/retry-health.ts | RetryHealth:哪些 Session 在等模型预算重试,供 models 健康项 |
src/app/role-models.ts | 解析 Eval 与 Executor 模型,并以角色模型初始化新 Session |
src/app/assessment.ts | MasterAssessor、研判 schema 与 orchestrator_eval 应答的窗口 |
src/app/execution-environment.ts | 执行环境:变量、Ticket 的取值、目录根与 prompt 段落的行——src/app/prompts.ts 把它们填入模板 |
src/app/source-context.ts | sourceContextOf:每个 Source kind 的 SourceContext |
src/app/links.ts | sourceLink(Trigger 消息所在页)与 candidateCard |
src/app/cards.ts | 给模型的 Trigger、Ticket、Thread、Decision 紧凑卡片,封闭的 Ticket 卡状态集 TICKET_STATES,及 textPage |
src/app/resources.ts | RESOURCES 与 readResource:orchestrator_read 按角色读取什么 |
src/app/tools/tool.ts | Tool 类型、readTool、两角色的 orchestrator_read,及 AcknowledgementSchema |
src/app/tools/main.ts | 主 Session 的工具 |
src/app/tools/executor.ts | Executor Session 的工具 |
src/app/http.ts | /orchestrator/api 路由:同源检查、请求上限与每类失败各一个状态 |
src/app/api.ts | Api:每条路由的参数、查询、body 与应答,及列表过滤器;只 import 类型,因此面板可打包它 |
src/app/views/text.ts | 视图文本的两种形态:preview(单行纯文本)与无 HTML 的 markdown;plain 与 readable |
src/app/views/records.ts | 视图共享的行:Trigger、Ticket、Thread、Decision 与 Channel 链接;ownersOf,一次请求所读后续消息的拥有者 |
src/app/views/tickets.ts | 按阶段的 Ticket 列表、Ticket 页、Mandate 依据事实与 Executor 负载 |
src/app/views/threads.ts | Thread 列表、Thread 时间线、搜索与 Decision 列表 |
src/app/views/sources.ts | Source 状态:每个实例的状态、最近一天的计数及当天过滤与丢弃的内容 |
src/app/views/settings.ts | 设置页视图:每块及其 revision、各 Source 设置及其 kind 表单、执行环境 |
src/app/views/overview.ts | 总览:需要人的 Ticket、问题、最近一天的流与 Executor 负载 |
src/executor/dsh.ts | DshExecutor、WorkerSlots、ticketExecution、inputMessage 与 mandateInstructions |
src/executor/background.ts | worker 在 DeepSeek Harness 中的后台工作:其 Session 拥有的 shell 任务(ctx.jobs)与子 agent(ctx.subagents):runningJobs、untilQuiet、killJobs、collectJobs、stopSubagents、stopBackground、until 与 cancelled(其回合被子 agent 停止所取消的 worker) |
src/infra/commands.ts | Commands,CLI 传输:run;限于配置仓库且经校验的 api 与 pages;作为 bot 的 lark;git。CliError 携带命令输出报告的代码(如 GitHub HTTP 状态);无输出的命令以其结束方式失败,绝不以其参数失败;validItems 跳过并统计列表中的畸形条目 |
src/infra/environment.ts | agent 的进程环境:命令 shim、git 配置与凭据助手、runAsWorker,及环境与 Ticket 目录的检查 |
src/infra/paths.ts | 包根目录,打包文件所在 |
src/infra/text.ts | 字数统计与会话消息上限、fill 与 sameName |
src/infra/health.ts | HealthBoard,每个部件的健康项,各由其唯一写入者认领 |
src/sources/kind.ts | SourceKind 协议 |
src/sources/registry.ts | SourceRegistry |
src/sources/host.ts | SourceHost、SourceContext、Health、health 与固定健康文本 |
src/sources/poll.ts | GitHub 与飞书轮询共享的部分:POLL_HORIZON_MS、polled、cursorAt 与 pollThreads(把失败作为 pollError 保留在其 Thread 上) |
src/sources/github.ts | GitHub kind 与插件入口,导出为 ./github-watch:一轮通知轮询、开放 Ticket 的 pull request 及其 CI、订阅 Thread |
src/sources/github-trigger.ts | GitHub 评论、review、Issue 正文与 CI 变更转为 Trigger;GitHub 地址 |
src/sources/github-pull.ts | pull request 规则:Ticket 跟随哪些 pull request(claimPull、observePull)、向 Engine 报告的关闭、pullEndNotices(Ticket 以 merged/closed 结束时的聊天通知) |
src/sources/github-channel.ts | GitHub Channel:评论与 review 线程回复,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 | 飞书消息转为 Trigger:实时事件、历史页与话题页共用一条规则 |
src/sources/feishu-loops.ts | 飞书循环:事件消费者、历史恢复、会话名与订阅话题轮询 |
src/sources/feishu-channel.ts | 飞书 Channel:话题回复,sent 或 gone |
src/sources/feishu-read.ts | Trigger 消息的 feishu/ 读取:其根、父级与话题;feishuThreadContext 连同图片交给研判 |
src/sources/admission-context.ts | 研判起步的有界窗口:Trigger 的 Thread 前几页(经其 kind 读取)与 Trigger 周边的消息 |
src/sources/feishu-image.ts | 把飞书消息的图片读为研判 Session 的附件 |
src/client/index.tsx | 客户端插件入口:侧边栏入口、主面板与 DeepSeek Harness 插件管理器中的页面 |
src/client/app.tsx | 面板:头部、搜索、词汇表、标签页与当前路由的页面 |
src/client/provider.tsx | 每个页面从宿主读取的内容:文案、调用、导航、通知、状态轮询,及 dsh-superagent 重启期间快速读取状态的 restart |
src/client/api.ts | 管理 API 的调用与轮询(由 src/app/api.ts 定义类型),及 Eval 状态的 Decision 过滤器 |
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 | 记录页:Thread 及其时间线;Ticket 及其授权、Input 与 Assignment |
src/client/settings.tsx | Settings 标签页:分组导航与当前分组 |
src/client/settings-kit.tsx | 设置块的构件:各阶段分组共享的设置、useBlockDraft、字段(含 Source kind 的)与保存历史 |
src/client/prompts.tsx | Prompts 设置:页面拥有其读取的视图、编辑器拥有草稿;Edit、Full prompt 与 History 标签据此渲染;内置部分的编辑器显示变更的默认并可保留或丢弃覆盖 |
src/client/prompt-config.ts | 按段落、按团队段落、技能名、内置部分(part/<key>,含 withOverride)与部分标题组织的 prompt 配置,及全量 prompt 的行级 diff,皆为纯函数 |
src/client/deployment.tsx | Deployment 设置,同时显示在 DeepSeek Harness 插件管理器中 |
src/client/draft.tsx | 设置草稿:draftReducer、其状态及与较新已存 revision 的合并、saveBarText(保存栏文字)与保存栏 |
src/client/controls.tsx | DeepSeek Harness 控件的本地副本,沿用其标记、键盘行为与 design token(bundle 不 import @deepseek-ai/dsh-client-ui-primitives);不读取 context |
src/client/widgets.tsx | 页面共享的小组件,读取面板 context:分区、页头、加载态、时间、链接、确认动作、setup 检查与 Markdown |
src/client/rows.tsx | 页面共享的行:Ticket、Thread、Decision、Thread 徽标,及自动消息连续段的折叠 |
src/client/markdown.ts | 报告与问题的 Markdown 子集,及消息中的裸地址,作为块与行内部件 |
src/client/text.ts | 按键的文案与领域值、原因、发送者、时间的措辞 |
src/client/marks.ts | 视图分类状态的标记:Ticket/Thread 阶段、Source 状态与 Decision 结果,各为圆点、图标、色调与标签 |
src/client/locales.ts | 双语文案,及 001 领域模型的词汇表 |
src/client/client.css、css.d.ts | 面板样式表,以文本加载 |
src/sources/github.ts 与 src/sources/feishu.ts 也是两个 Source 插件的入口,由 tsdown.config.ts 构建,并在 package.json 中导出为 ./github-watch 与 ./feishu-watch。bundle 的 cordis.patch.yml 插入三个插件,飞书默认禁用。
测试
| 测试 | 覆盖 |
|---|---|
tests/core-engine.spec.ts | 内存仓储上的领域规则:接收、Eval、扇出、恢复与停止、被中断的 Assignment、暂停、Policy 变更、报告与结果、附加、回复、失败送达与被消除的问题 |
tests/stage.spec.ts | Ticket 阶段,逐行 |
tests/policy.spec.ts | Source 范围、gate、准入表、Mandate 重新校验、Policy 版本、设置迁移与研判结果 |
tests/registry.spec.ts | SourceRegistry:实例、校验、Policy 部分、Channel 与 Thread 上下文 |
tests/sources.spec.ts | 在 SourceContext 上的飞书与 GitHub Source:翻译、轮询与读取位置、pull request 规则、健康 |
tests/commands.spec.ts | 以部署各账号发出的 CLI 调用、Channel 应答、限仓库的 GitHub 调用 |
tests/feishu-read.spec.ts、tests/github-read.spec.ts | 飞书与 GitHub 读取 |
tests/admission-context.spec.ts、tests/feishu-image.spec.ts | 研判的 Thread 窗口与飞书图片读取 |
tests/assessment.spec.ts | MasterAssessor 每个 Eval 保留什么、何时放手 |
tests/background.spec.ts | 等待 worker 的 shell 任务与子 agent:唤醒它的结束,与停止的宿主 |
tests/bundle-patch.spec.ts | bundle 的补丁按 DeepSeek Harness 的方式读取,及其为 Executor Session 定义的 agent 预置 |
tests/effects.spec.ts | effect 队列:合并、上限、退避重试、健康与关闭 |
tests/passes.spec.ts | Passes:一次一个、pass 期间被请求的紧随其后作为一个运行、失败 pass 报告的运行时健康 |
tests/outbox.spec.ts | 运行中 Runtime 上的 Outbox:处理暂停或 Source 未安装时消息等待 |
tests/cache.spec.ts | TextCache |
tests/queue.spec.ts | 经 effect 队列的 Runtime 工作,及更早版本存储的内容 |
tests/followups.spec.ts | Ticket Thread 上人的后续消息:给主 Session 的通知、处理它的工具、一次爆发的代价;worker 的交还与上下文跟随、主 Session 的备注 |
tests/health.spec.ts | 每个部件一条健康项,各自被下一次成功清除 |
tests/delivery.spec.ts | AgentDelivery |
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 期间的 Trigger、送达与启动期间变化的 Ticket |
tests/http.spec.ts | 同源与回环检查、每类失败的状态、body 上限、路径参数与查询 |
tests/settings.spec.ts | 与模型或账号检查竞速的设置保存、被保存赶超的 Policy 同步,及暂停、同步与块的拒绝 |
tests/views.spec.ts | 管理视图从 Store 推导什么,例如被丢弃消息所属的 Thread |
tests/timeline.spec.ts | Thread 的分页与 Trigger 按时间再按 id 的邻居;每个已收 Trigger 的时间同构 |
tests/migration.spec.ts | schema 11 迁移:每条已存记录的当前形态,及对更旧 schema 的拒绝 |
tests/store.spec.ts | 仓储及其工作集:SQLite 与内存一致;kv 区,每个 KV 键只在 src/kv.ts 与迁移中拼写;设置历史与查询 |
tests/prompts.spec.ts | prompt 校验、组装与版本 |
tests/cards.spec.ts | 内置文本必须陈述的规则,逐文件 |
tests/model-surface.spec.ts | 模型读到的一切:工具、prompt、技能与输入,对照工具 schema、执行环境与词汇表检查,按角色限定尺寸预算 |
tests/deployment.spec.ts、tests/environment.spec.ts、tests/index.spec.ts | 插件配置与 setup、agent 的进程环境、插件入口 |
tests/service-script.spec.ts | scripts/dsh-superagent 对假 ps、tmux 与 dsh |
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 | 设置分组、草稿与历史、prompts、Deployment 页,及面板的搜索、词汇表与路由记忆 |
tests/draft.spec.ts、tests/prompt-config.spec.ts、tests/markdown.spec.ts、tests/marks.spec.ts | 草稿状态、合并与保存栏文字;按段落的 prompt 配置与行级 diff;Markdown 子集及其链接;视图每个分类状态的标记 |
tests/locales.spec.ts、tests/styles.spec.ts | 双语文案、样式表 |
tests/class-docs.spec.ts | src 中每个类前都有 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 | 每个 Trigger,其时间 at 取 Date#toISOString 的唯一形态,Engine 接收时设定,因此时间可按文本比较 | Engine |
disposition | 每个 Trigger 的去向:带 Source 理由代码的 filtered、其 Eval 与 Decision、它到达的 Ticket(扇出或附加)、是否被重新排队、交还它的 Ticket 及理由、其 Executor 确认或回答过它的 Ticket | Engine |
trigger_thread | 每个 Trigger 在哪些 Thread 上,含其时间与是否人所写——读取 Thread 的 Trigger 无需扫描全部 Trigger,按时间再按 id 排序:Thread 时间线的一页从 <at>|<id> 游标续读,同时刻的 Trigger 不会被跳过 | Store,随每个 Trigger |
thread | Thread:Subscription、因上下文跟随它的 Ticket、排队的 Trigger、进行中的 Eval、过期计数与 meta;生成列 active(Trigger 排队或 Eval 运行时置位)与 subscribed(有 Ticket 订阅时置为其 Source) | Engine;Source 写 meta |
ticket | Ticket 及其 Mandate、origin、execution、Assignment、Channel、Input 与跟随的 Thread;生成列 open(未结束时置位)与 active(Input 未送达或最新 Assignment pending/running 时置位) | Engine |
decision | Decision,按 Thread | Engine |
effect | effect 队列:按 kind 与 key——载荷、顺序、带最后错误的失败尝试、到期时间 | Effects;schema 11 迁移把更早版本为主 Session 保存的内容排队 |
outbox | 每条 Channel 消息的回执:按 id,含所属 Ticket 与 status(pending、sent 或 dropped),保证一个 id 绝不二次排队;丢弃消息的何时与为何——生成列 dropped(时间)与理由代码。等待中的消息有一条 reply effect,由它持有尝试计数 | Outbox |
kv | 其余所有记录,按键 | 指名它的那个部分 |
生成列由其行的 JSON 计算,集合之外为空,且各在其集合上建索引,因此一个 pass 只读活跃工作。早于某列的数据库在 Store 打开时获得该列。
KV(src/kv.ts)定义每条键值记录的拥有者、键与类型。所有读写使用这些条目;只有迁移代码指名更旧的键。
| 记录 | 键或内容 |
|---|---|
| Schema | 当前 schema 版本 |
| 设置版本 | settings-revision、settings-version:<n>、settings-block-revision:<block>;快照含来源、Policy 版本与受影响 Ticket |
| Prompt 版本 | prompt-revision、prompt-version:<n> |
| 主 Session | master-id |
| Session prompt 历史 | session-prompts:<session id> |
| Source 状态 | 读取位置(如 github-cursor:<number>)、PR CI 指纹与已见飞书话题消息;键以 kind 或 thread- 加 Thread 键开头 |
| 显示名 | source-labels:<source id> |
消除记录在其 Decision 上。Input 文本在读取时从 Ticket 渲染,因此两者都不需要单独的键值记录。
读缓存使用内存限量的 TextCache 实例,重启即失。Session 分页读取 Source 原件或 prompt 预览时,重启后必须重读偏移 0。intake 之后解析的飞书链接同样缓存;基于链接的搜索只有在当前进程生命周期内读过该链接之后才能找到它们。
尚无任何东西删除旧记录,因此它们无限增长:trigger、disposition 与 decision 行;已结束的 Ticket 及其 Thread;outbox 回执;以及 kv 中每次保存的设置 revision 与 prompt 版本、每个 Session 工作所用的 prompt revision、Source 按 Thread 与按 Issue/pull request 的记录。
SCHEMA_VERSION 只增不减。更新版本的数据库拒绝启动;src/app/migration.ts 在启动时把预发布 schema 10 的数据库一次性升级,并拒绝更旧的。
面向模型的接口
主 Session 工具
主 Session 有 14 个应用工具:
| 用途 | 工具 |
|---|---|
| 读取与路由 | orchestrator_read、orchestrator_route |
| 管理 Ticket | 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 命令运行。它也允许从卡片直接忽略 inferred 候选。orchestrator_ticket_note 以上下文发送事实,不是授权。
设置保存选择 policy、eval、executor、pause 或 source;最后一个还需指名其 Source id。preview: true 的 prompt 保存只组合文本不保存。
主 Session 还有 DeepSeek Harness 的 skill、bash、read、read_image、job_output、job_list 与 job_kill。避免读代码是 prompt 规则,不是工具限制。
预置与等待上限
新的主 Session 使用 dsh-superagent-router(ROUTER_PRESET)。shell 命令与任务等待 5 秒后返回。若 Web UI 在第一回合前选了别的预置,Sessions 会恢复本预置(worker 则恢复 WORKER_PRESET)。
src/app/sessions.ts 的 noWait 守卫拒绝主 Session 里在命令位置调用 sleep 的 shell 命令、超过 30 秒的 shell timeoutMs,以及超过 5 秒的 job_output 等待。报告与任务结果自动到达,等待只会延误到来的候选。
standard 上的既有主 Session 也需要为前台命令与任务等待显式设超时;否则该预置的默认分别占住步骤 60 与 30 秒。宿主经 presetForSession 判定当前预置,回退到 Session 投影或头部,读不到时假定 standard。
Executor 工具
每个 Executor 有六个应用工具:
| 工具 | 用途 |
|---|---|
orchestrator_read | 读取对其自身 Ticket 可用的资源 |
orchestrator_ack | 确认一个请求 |
orchestrator_report | 报告进展或结果,按 pull request 规则绑定 pull request |
orchestrator_thread_unrelated | 丢弃无关的附加 Thread,交还其拥有的后续消息,并告知主 Session 原因 |
orchestrator_thread_follow | 为上下文订阅相关 Thread,并告知主 Session |
orchestrator_trigger_handback | 一次性交还一条消息,理由随其候选携带 |
当另一 Ticket 仍拥有该消息、或本 Ticket 从未拥有它时,交还返回 queued: false。例如 bot 消息已独立进入 Eval。
worker 预置定义在 cordis.patch.yml,经 WORKER_PRESET 选择。src/app/sessions.ts 的 WORK_TOOLS 保留可用的文件、搜索、shell、任务、web、技能、todo 与子 agent 工具。worker 不获得向人提问或呈现、更改 profile、在 worker 槽位之外调度工作、启动或消息其他 Session、编辑外部文档的工具。Ticket 结束后工具调用被拒绝。
结果形状与研判上下文
路由结果使用 {decision?, reason, ticket?}。orchestrator_route 返回 {results},保持项序并附加各项的 trigger_id 与 kind。无效或被拒的项返回 refused 与理由,而不中断批次。
设置与 prompt 保存返回已保存的配置。Policy 同步返回 Source、允许列表大小,及 added 与 removed 的名字。
orchestrator_eval 返回上下文而非指示。研判指示位于 runtime/admission(prompts/admission.md),规则位于 runtime/policy。其上下文包括:
- Trigger 文本,至多 4,000 字符,含图片 id 与批次中的其他 Trigger。
- 至多八个
related_tickets,开放者优先,及tickets_total。 - Eval 的
policy_version。 - 一个 Thread 窗口:前五条消息与 Trigger 两侧各五条。消息用
{id, from, at, text},通常限 400 字符,两侧各两条为 1,200。被截断的消息带chars;图片引用用images。 - 其余已加载消息为 50 字符的
{id, from, preview}桩。orchestrator_eval_read按 id 展开。
Ticket 卡片使用 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> | 单个 Decision |
thread/<key> | 标签、Ticket、近期 Decision 与一页 Trigger |
trigger/<id> | 消息文本、被回复的 Trigger 与邻居消息,不含 CI 运行 |
ticket/<id> | 相关 id、来源批次 Trigger、Thread,及 Assignment 与 Input 的首页 |
| Ticket 子资源 | /assignments、/inputs、/assignment、/result、/report/<assignment id>、/transcript、/message/<n>、/input/<id> |
settings、settings/<block> | revision 与摘要,或可整块直接保存的完整块 |
prompts、prompts/<revision>、prompts/current、prompts/defaults、prompt-preview/<id> | prompt 配置与组合后的文本 |
models、models/<provider> | 可用模型 |
feishu/<trigger id>/…、github/… | Source 原件,见 Source 技能中的描述 |
Ticket 的 Thread 标记 context(仅上下文跟随)或 bound(Source 绑定)。Assignment 页最新在前并含报告预览。用 next_offset 续读列表。/input/<id> 接受 Input 的名称或 id。settings 完整包含小块并概述 Policy 与 Source 块。
Source 资源含消息邻域、github/pulls/<n>/thread/<comment id> 与 github/commits/<sha>/checks。
原件的消息以单一紧凑形态读取——{id, from, at, text, reply_to?, path?, line?, url?}——而非 GitHub 或飞书的原始记录形态。
Executor Session 只读自己的 Ticket、其 Thread、到达它的或其请求持有的 Trigger、它工作所用的 prompt 版本与原件;其页面不含它读不到的东西的 id——Decision 与其他 Ticket。
页面显示的每个 id 都经这些资源读取,因此 Session 能从一个对象走到下一个;没有可读的哈希(如 Eval id 或 Policy 版本)不出现在页面上。
文本页默认 4,000 个 UTF-16 码元、至多 8,000;列表默认 20 项、至多 50 项。
更早版本的 event/、task/ 与 observation/ 名称仍可解析。
送达 Session 的消息
这些消息被引入主 Session 的回合:
| 消息 | 内容 |
|---|---|
| 候选 | 批次 Trigger 卡片、Thread、related_tickets、tickets_total、delivered_to、Issue/PR 链接引用的 Ticket、handed_back 理由与下一步动作 |
follow_up | 一条消息及其 Executor 所见、replies_to、接收的 Ticket,及「由 Executor 裁决、主 Session 只能停止工作」的提示 |
worker_report | Ticket 卡片、来源 Trigger、状态与至多 600 字符的摘要;截断时带 chars 与读取指针 |
worker_attention | 携带问题的 Ticket 卡片 |
其余通知在不唤醒主 Session 的情况下注入:ticket_ended、settings_saved、prompt 保存、Trigger 重新排队、丢弃的 Subscription 与上下文跟随。设置通知指名块、变更路径与 read: settings/<block>。主 Session 自己执行的保存、重新排队、退订或附加不会收到冗余通知。
worker 内联接收 assignment 与 Input。首条消息含标题、至多 1,500 字符的 assignment 文本与来源 Trigger 卡片。Input 携带至多 600 字符的 Trigger 卡片与 replies_to,或纯 Input 文本。读取指针只在内容被截断时出现。
仅面板的动作
没有工具用于:消除 unavailable Decision、重跑 setup、刷新 Source 显示名、或把 Executor 拥有的人后续消息发给 Eval——最后一个被 orchestrator_trigger_requeue 有意拒绝。其余面板动作都有等价工具。
大部分内置指示位于 prompts/*.md,每行一句,由 promptFile 连接。Source 技能位于 skills/dsh-superagent-*/SKILL.md。工具描述与固定通知(如重启恢复备注)保留在代码中。研判指示只位于 prompts/admission.md。
tests/model-surface.spec.ts 检查这些表面的漂移。团队可编辑的部分见 004 Prompts。
配置重载
除 stateDir 外每个插件配置键都是易失的。面板的 Deployment 设置与 DeepSeek Harness 插件管理器经其配置表单编辑它们;宿主写入 cordis.patch.yml 并就地更新值,dsh-superagent 在使用时读取各上限,因此变更无需重启即生效。
更改 setup 读取的 Source 字段(如账号 CLI)的设置保存、以及 setup 等待期间的任何保存,都会重启 dsh-superagent。DeepSeek Harness 的设置服务在该重启期间不提供该条目,因此其自身重启后 dsh-superagent 会请求再次读取该条目,打开的表单也随之恢复可编辑。
用真实数据评审变更
把 DSH_HOME 指向临时目录并在其中创建新 profile。从备份复制数据库,禁用所有 Source,暂停处理并移除主 Session id。给 GitHub 与飞书 CLI 只读替身。
开放问题
- 信任:哪些 Trigger 字段可信——如 Source 的 actor 与 Thread 归属——哪些不可信——如正文与引用。
- 部署隔离:agent 的 profile 使用自己固定版本的 DeepSeek Harness 安装,使别处升级 DeepSeek Harness 不至于破坏它。