dsbot-session-create

中文文档
0.5.0发布于 2026/11/03MITES Module零依赖

一个DSH插件,为会话提供了四个工具:create_session(在指定的工作区或现有文件夹中打开一个新会话,将其模型固定到调度会话并向其传递一个提示),session_message(将消息剪切到一个会话中,该会话是st

中文使用文档
文档目录16 节
  1. 为什么需要它
  2. 工具
  3. create_session
  4. 派送台账
  5. session_message
  6. read_session_transcript
  7. list_dispatched_sessions
  8. 状态是怎么来的
  9. 派完自动回执
  10. 宿主启动自检(重启后自动接续)
  11. 失败时的行为(fail-closed)
  12. 装
  13. 开发
  14. 几个必须知道的事实
  15. 已验证 / 尚未验证
  16. 许可

dsbot-session-create

给 DSH 加四个工具:

  • create_session —— 在指定的 DSH 工作区、或任意一个已存在的文件夹里开一个新会话,并把一段提示词投进去。开之前先把模型和权限档定下来。
  • session_message —— 给一个还在跑的会话插一条消息(插队投放,它下一步就读到)。
  • read_session_transcript —— 按会话 id 把它说过的话和当前状态读回来。
  • list_dispatched_sessions —— 看这个插件派过哪些会话、各自此刻什么状态。

它是一个 DSH 插件(dsh-plugin),宿主半边,没有界面。

三条与普通「开个会话」不同的硬规则,先看这里:

  1. 派出去的会话不落到宿主的全局默认模型上。它钉死到派活那个 Bot 自己正在用的模型;读不到派活人的模型就不建这一单。
  2. 缺省权限档是全权限。不传 permission 时一律开在 danger-full-access,并把这一条写进结果;要 auto 就显式传。
  3. 补话只服务还在跑的会话。目标会话已经停下,就拒投并让你新开一个,不往历史里一层一层堆活。

为什么需要它

DS Bot 的 Chief 能把活派给别的 Bot(message_bot),但派不进「某个项目的那个会话」。 宿主本身早就有这个能力(ctx.sessionController.create({ cwd | workspaceId })),只是没有对外开放成工具。

这个插件就是那层薄壳:把宿主已有的原语开成工具。 参考实现是 DS Bot 自己的 src/host/roster.js(帮 Bot 建会话时用的同一批调用)。

工具

create_session(title, prompt, workspace? | folder?, preset?, permission?, model?)
session_message(sessionId, message)
read_session_transcript(sessionId?, last?)
list_dispatched_sessions(limit?)

create_session

参数必填说明
title是新会话的名字,显示在会话列表里
prompt是投进新会话的那段话(排队投放,mode: 'queue')
workspace二选一工作区 id,或它的完整文件夹路径
folder二选一任意一个存在的文件夹的完整路径
preset否agent 预设(standard / minimal / ptc / creator),建会话时写进 header,开跑后不能换;名字错就整单不建
permission否权限档,见下面「权限档」
model否写成 provider/model;不写就与派活的那个 Bot 一致

workspace 和 folder 必须给且只能给一个。

两者的区别:

  • workspace —— 会话挂在那个 DSH 工作区下,会出现在用户的对应项目里。
  • folder —— 会话按 cwd 创建,不加入任何 DSH 工作区,用户的项目列表保持原样。

两者是同一套宿主原语的两种落位方式,没有别的差别。

模型(fail-closed)

宿主 sessionController.create 不转传 model,所以不钉就是「宿主的全局默认模型」;2026-11-01 那个默认一度是按量付费的模型,派出去的会话因此替用户烧过真钱。所以:

  • 不写 model 时,取派活那个 Bot 自己会话正在用的模型(agent.session.requestHeader().config 的 provider / model / reasoningEffort)。
  • 不写任何模型名做兜底:写死一个名字,provider 或组一改名,这条绑定就静默失效。
  • 显式写 model 必须是 provider/model——按第一个斜杠切一刀、两边都要有字;provider 空或 model 空就整单失败(model-invalid),model 本身允许再带斜杠(b/c)。
  • 派活人的模型读不到就不建这一单(dispatcher-model-unknown)。宁可失败,也不留一条会落到全局默认模型上的会话。
  • 建会话之后用 sessionController.selectModel 钉死。这一步失败不中断派送,但结果里带一句 modelNote,明说「没钉上、可能落到宿主默认、花额度之前先查它的模型」。

权限档

档名由宿主判定(/permission 的合法值会随宿主升级漂移),本插件不硬编码白名单,只做三件事:

  • 显式传 read-only / workspace-write 会被改写成 danger-full-access,并在结果里如实标出 permissionOverriddenFrom。理由:这两档带 approval: ask,派单会话无人应答就整轮卡死,而宿主没有「给已存在会话提权」的入口。要放行只读探针,在宿主环境里设 DSBOT_ALLOW_RESTRICTIVE_PERMISSION=1(值必须严格等于 1)。
  • 没传 permission 时一律开在 danger-full-access,并在结果里带 permissionNote 说明。理由:auto 档下宿主 auto-review 审查每一次工具调用,而派出去的会话无人值守——审查器没按约定给出判决时,宿主直接按拒绝处理、不重试也不降级,会话就卡在原地。要 auto 就显式传。
  • 没有 commands.execute 时设不了档:保持不传,吃宿主默认(本机默认就是全权限),并在结果里说明。
  • 显式要 auto 时:宿主名单明确没有就整单失败、不建会话(host-missing);名单读不到就照旧下发,让宿主的 /permission 自己去判认不认这个名字。
  • auto 档的第二步是把审批策略写成 never:auto 自带 ask,无人值守时 reviewer 判 deny 会变成整晚卡死。这一步走 session.append('approval/policy', { policy: 'never' }),不调 ApprovalService.setPolicy(后者会往会话里注入一条假消息);写不进去就归档这个会话并整单失败——让会话带着 ask 活下去,等于埋一颗整晚卡死的雷。
  • 有一处宿主机制产物要知道:/permission auto 那一步自己会写一条 ask,并往会话里注入一句「changed from "never" to "ask"」的 user 消息;本插件紧跟其后写 never,最终生效的是 never,但读 transcript 时会看到那条消息。

派送台账

每次 create_session 成功,都会往派送台账追加一行:

$DSH_HOME/bot/session-create/dispatched-sessions.jsonl      # 可用 DSH_SESSION_CREATE_LEDGER 覆盖

每行记住:会话 id、标题、落位、提示词开头、派送时刻,以及这一单实际下发的 preset / permission / permissionOverriddenFrom / approvalPolicy,和派活人的会话 id(回执按它投回去)。建了会话、但权限档这一步失败的单会补写一行 failed(src/core.ts:679),否则事后谁都在会话列表里找不到那个空会话;提示词投递失败走另一条路——归档会话并报 prompt-failed,不写台账。

台账写失败不会让整次派送算失败(会话已经建出来了),但结果里会带 ledgerError,并写明「自己手动记下这个 session id」。

落点为什么在 bot/ 下:这份账是 Bot ↔ 人的交接记录,属于 DS Bot 自己的数据,所以跟在 DS Bot 的 home(<DSH_HOME>/bot)里。若 ds-bot 的 home 被它自己的 profile 配置改到别处,用 DSH_SESSION_CREATE_LEDGER 对齐。

session_message

给一个已经存在、且还在跑的会话插一条消息,不新建会话:

参数必填说明
sessionId是目标会话的 id(create_session 或 list_dispatched_sessions 给的)
message是插进去的那段话
  • 走 controller.prompt(mode: 'steer')(宿主 agent.steer):插进正在跑的那一轮,会话下一步就读到,不等这一轮结束。
  • 目标会话的 turn 已经结束(宿主直报 idle)时拒投(session-idle),并指向 create_session。判据只用宿主直给的状态,不替调用方判断「这条消息算新任务还是纠偏」——那是语义判断。这条是 2026-11-01 那单事故的防线:同一条会话被补了 16 轮裁决,历史涨到约 76 万 token,第二天宿主把它切到 512,000 窗口的模型后立刻放不下。
  • 宿主没报状态时按「还在跑」处理,结果里如实标 targetState: unreported。
  • 只说「已插进正在跑的那一轮」,不说「它看过了」——后者只有读回才能证明。
  • 单次投递超时 10 秒(MESSAGE_TIMEOUT_MS)。

read_session_transcript

按 id 取回一个会话说了什么、现在什么状态(最新若干条消息 + 当前这一轮结束没有)。不给 sessionId 就取台账里最近一条派送,所以「刚派完那单跑完没」不需要手动找 id。

last 给取回多少条(1..200,默认 40),更早的用更大的值再读。历史读回超时 20 秒(HISTORY_TIMEOUT_MS)。

list_dispatched_sessions

倒序列出台账里的派送,每条带此刻的状态:还在跑 / 上一轮已结束 / 还没起跑(not started)/ 读失败。用来挑「哪些可以收了」。

状态是怎么来的

会话「跑完没有」有两条来源,插件优先问宿主,拿不到才从会话自己的事件推:

  • 宿主直给(agent.status 是 running / idle)→ 输出写 the harness reports this Session as running;
  • 事件推断(只有 turn/start 没有 turn/end)→ 输出写 read from the Session's own events。

两种来源的措辞故意不同:推断不许冒充宿主结论。

读回走宿主自带的 ctx.sessionController.page({ address, throughSeq, maxMessages }, signal),参数形状逐字照抄 DS Bot 自己的三处生产调用(src/host/parts.js:1287、chat-index.js:58、usage.js:351)——不自己造一套读事件的机制。宿主内置的 session_event_read / session_search 工具对普通会话不可用(返回 No session matched "…"),故不走。

派完自动回执

派送时把派活人的会话 id 记进台账。目标会话正常收尾(turn/end.reason.kind === 'completed')、安静满 120 秒(QUIET_WINDOW_MS)、宿主复核为 idle,并且它自己派出去的活都已经停下来,插件才用 controller.prompt(mode: 'queue') 投一条短回执回派活人(src/core.ts 的 createDispatchNotifier,src/index.ts:299 的 installDispatchNotify 挂到 ctx 上——没人调用工具时也要有东西替我们盯着)。

投递面是收窄过的:把假回执(关停、重启掐断、报错、卡住)和分段派工(还在等自己派出去的活)这两大类挡掉。但要说清楚——turn/end 这一层看不出「这一单」的边界,收窄不等于只剩整单完成。2026-11-03 拿本机派单台账 126 单逐条回溯(含静默窗口与三道门):旧口径会投 160 条回执,新口径 154 条(少 3.75%,其中真正被门三拦下的是 0 条);120 单仍会收到至少一条,11 单会收到多条,最多的一单 11 条。想再降只能改协议(例如一次派单只投一条回执),不在这一版里。

  • 只认正常收尾:aborted(关停/叫停)、interrupted(重启掐断)、error、blocked、max-tokens,以及认不出来的取值,一律不进静默窗口、一个回执都不发(COMPLETED_REASON,门一)。取值来自 2026-11-03 全扫本机 <DSH_HOME>/data/sessions/**(当日 1,740 份文件、7,851 条 turn/end)的统计:completed 6,914 / aborted 464 / error 171 / blocked 153 / interrupted 121 / max-tokens 29。
  • 还在等自己派的活就不算收口:台账里 dispatcherSessionId 指向它、宿主又报着没停的子会话,说明它是在等它们(firstBusyChild,门三)。等的时候不投;子会话停下、它被叫醒再跑一轮、这一轮没有在跑的子会话,才投。
  • 机械区分不了的那一类:自己没派人、也没出错,就是这一轮干完了停下——这与「整单完成」在宿主事件面上同形,插件不替派活方猜,照旧投一条;由派活方自己按纪律决定要不要理它。

回执走 queue 不走 steer:它是给派活人的旁路通报,排到下一轮,不去打断他手头正在跑的那一轮。

宿主启动自检(重启后自动接续)

宿主重启时在跑的 Bot 会话一并被杀,醒来后没有东西会主动说「刚重启过、盘上有接手指针」。这一段补上它(src/autoresume.ts,src/index.ts:303 的 installAutoResume):

  • 只认两个文件:指针 restart-pending.json,已确认记录 restart-pending.ack.json。目录运行时算,不写死:DSH_AUTORESUME_STATE_DIR > <DSH_HOME>/bot/session-create(DSH_HOME 缺省 ~/.dsh,与派送台账同一目录;src/autoresume.ts 的 resolveAutoresumeStateDir)。要接到别处就设那个 env,不用改代码。
  • 目标只能是指针里的 notify_session_id:缺失或不是字符串就跳过。不猜主会话、不遍历会话列表。
  • 投三次:间隔 6s / 12s / 20s(src/autoresume.ts:50,AUTORESUME_RETRY_DELAYS_MS;38s 是三次间隔之和,不是总超时——单次投递上限 10s)。宿主刚起来那几秒恰恰是最可能投不进去的时刻,所以单跳不够;只有 send-failed 才排下一次,送达、已确认、没指针、没目标都是终态。
  • 幂等只看 written_at:已确认记录里的 written_at 不严格新于指针的 written_at 就跳过。失败也写一份 ack,但故意不写 written_at——它只是盘上的证据,不参与幂等判断,所以下次启动照常重试。
  • 只投 prompt(mode: 'queue'),不建会话、不改会话状态。唤醒正文只给指针的绝对路径和固定一句「上一个宿主 pid」,让收到消息的一方自己去读盘,不抄正文。
  • 关掉它:DSH_AUTORESUME_DISABLE=1(在读任何文件之前就判断,连定时器都不注册)。
  • 全程 fail-open:读不到盘、JSON 坏、没有目标,都只记一行日志就安静退出;定时器 unref,不阻塞宿主启动也不拖住宿主退出。

失败时的行为(fail-closed)

位置不成立就什么都不建,直接报错:

情况结果
文件夹不存在报错,不建会话
工作区认不出报错,不回落到默认工作区
workspace/folder 两个都给或都不给报错
preset 名字宿主不认报错,不建会话
model 不是 provider/model报错(model-invalid),不建会话
派活人的模型读不到报错(dispatcher-model-unknown),不建会话
显式要 auto、宿主名单里没有报错(host-missing),不建会话
宿主缺所需服务报错,附上缺了哪几个
权限档设不上归档这个会话,整单报错
auto 的审批策略写不进归档这个会话,整单报错
提示词投递失败归档这个会话,如实报错,不对外说成功
补话给已经停下的会话拒投(session-idle),什么都不发

那张「宿主缺服务」的报错要能真的报出来,靠的是服务不写进 inject 硬声明:插件只 inject: ['tools'],其余服务都走 ctx.get() 宽容路径(src/core.ts 的 resolveServices)。写成硬 inject 的话,宿主缺服务时整棵插件树会加载失败,preflight 那句可读报错根本没有机会执行——用户只会看到「工具不存在」。

这里和 DS Bot 的 roster.js 有一处有意不同:它会「工作目录没了就悄悄换到团队工作区」,本插件不这么做——悄然换位置会让人以为任务落在 A 项目,实际跑在别处。

归档失败时,报错里会改成说「列表里可能还留着一条空会话」——不谎称已归档。

装

推荐以链路方式装进 profile:

# 在 profile 目录里(<DSH_HOME>/profiles/<profile>)
# 把 "dsbot-session-create": "link:<本插件目录的绝对路径>" 加进 package.json 的 dependencies

或直接把本目录复制进 node_modules/。装完 必须重启 DSH(重启只能由用户执行)。

cordis.patch.yml 负责把它挂上:

- insert:
    - id: session-create
      name: dsbot-session-create

开发

bun run build   # src/*.ts → index.js(@deepseek-ai/dsh-tools 保持 external)
bun test        # 165 个测试、5 个文件;plugin.test.ts 测的是构建产物,跑前先 build

类型检查(本仓不带 typescript 与 @types/node 依赖,用现成的那份就行):

node <某个 node_modules>/typescript/bin/tsc --noEmit -p . --typeRoots <同一个 node_modules>/@types

--typeRoots 显式指过去,是因为 tsconfig.json 里没有写任何本机路径:它只声明 "types": ["node"],落在哪由你调用时决定。

几个必须知道的事实

  • @deepseek-ai/dsh-tools 不在 profile 的 node_modules 里(在宿主 app.asar 内,加载插件时注入)。所以 test/stubs/dsh-tools.ts 是它的替身,tsconfig.json 的 paths 只为本机开发把它指过去——那不是权威类型定义,是按 DS Bot 的实际用法反推的最小形状。
  • 宿主 API 的形状(create 返回 { sessionId }、prompt 要 requestId + mode + content:[{type:'text',text}])是从 DS Bot 的源码读出来的,不是从官方文档抄的。上游没有这层文档。
  • 代码与文档里不许出现本机路径(2026-11-02 用户定):源码、测试、注释、配置一律不许写盘符路径、用户名或本机目录名。唯一例外是单元测试里验证「Windows 路径怎么处理」用的虚构样本(test/read.test.ts、test/core.test.ts 与 test/autoresume.test.ts 里那几个),它们跟这台机器的实际目录无关。默认落点由 DSH_HOME / homedir() 在运行时算,要换就加一个 env 覆盖;test/autoresume.test.ts 有一条用例扫 src/ 下全部 .ts(不递归子目录),命中盘符形式的路径就判红。
  • 启动自检的落点跟随上面的规则:换机器、换 DSH_HOME 都不用改代码;要把指针接到某个固定目录,设 DSH_AUTORESUME_STATE_DIR。
  • 派出去的提示词走 queue,补话走 steer,回执走 queue——三个通道口径不同,改的时候别顺手统一。

已验证 / 尚未验证

已验证(2026-11-03 实测)

  • bun run build 之后 git status 只见预期改动:index.js 与 src/ 同步(64,495 字节,sha256 040124bb…989)。
  • bun test:176 pass / 0 fail / 654 次断言,5 个文件(bun 1.3.13)。
  • tsc --noEmit -p . --typeRoots <现成的 node_modules>/@types(借一份现成的 typescript 与 @types/node,见「开发」):0 个错误。不带 --typeRoots 会报 error TS2688: Cannot find type definition file for 'node'. 并退出码 2——本仓没有自己的 node_modules。
  • 0.2.0 那份构建的描述文本确实在真实宿主里加载过:宿主会话的工具表里 create_session 带上了 model 参数与 auto 权限段(never the host's global default、DSBOT_ALLOW_RESTRICTIVE_PERMISSION),说明 profile 挂的就是本仓的 index.js。0.4.0 与 0.5.0 的改动都要重启宿主之后才生效(重启只能由用户执行)。

尚未验证

  1. ctx.sessionController.page 在真宿主里是否真按 { records, hasMore } 返回(参数形状抄的是 DS Bot 生产代码,不是官方文档)。
  2. steer 插队以后,目标会话是否真在下一步就读到(只验到「宿主受理」这一层)。
  3. auto 档写 approval/policy: never 在真宿主里是否被认(单测覆盖的是「写不进去就归档并报错」这条路)。
  4. 启动自检的真重启投递:注册、延迟序列、幂等判据都有单测,但没在真重启里收过那条唤醒消息。
  5. 提示词的 30 秒超时上限没有「真触发」用例——bun 1.3.13 的 AbortSignal.timeout 在进程没有其它待办定时器时不触发(宿主是 Node,不受影响)。
  6. last 夹到 1..200、limit 夹到 1..20 是本插件自定的范围,宿主有没有更低的天花板未验。

许可

MIT

版本与发布记录

dsbot-session-create 共发布 6 个版本,首次发布于 2026/10/30;过去 12 个月发布了 6 个版本。

版本发布日期距今
0.5.0latest2026/11/03今天
0.3.02026/11/021 天前
0.2.02026/11/021 天前
0.1.12026/10/304 天前
0.1.02026/10/305 天前
0.0.0-stage2026/10/305 天前

关于 dsbot-session-create 的常见问题

dsbot-session-create 是什么?

一个DSH插件,为会话提供了四个工具:create_session(在指定的工作区或现有文件夹中打开一个新会话,将其模型固定到调度会话并向其传递一个提示),session_message(将消息剪切到一个会话中,该会话是st

如何安装 dsbot-session-create?

在项目目录运行 npm install dsbot-session-create,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:@deepseek-ai/cordis(*)、@deepseek-ai/dsh-tools(*)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

dsbot-session-create 支持 TypeScript 吗?

该包的 package.json 没有声明类型入口。需要类型提示时,可以在 npm 上查找社区维护的 @types/dsbot-session-create 是否存在。

dsbot-session-create 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

dsbot-session-create 可以商用吗?

dsbot-session-create 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

dsbot-session-create 还在维护吗?

最新版本 0.5.0 发布于 2026/11/03(今天);至今共发布 6 个版本,其中过去 12 个月发布了 6 个;首次发布于 2026/10/30。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。