- Home
- Plugin
- Integrazioni e accesso remoto
- dsh-aiyayi
dsh-aiyayi
bayihy/dsh-aiyayi
Companion persona preset with persistent memory, presence heartbeat, idle updates, and a delegated helper agent with built-in fetch and CDP web tools.
Installazione
dsh plugin --profile web add github:bayihy/dsh-aiyayiREADME
阿依娅伊(aiyayi)
一个装在 DSH 里的对话伙伴 preset:有独立人格、有自己长期的记忆、说话像人;她本人只负责聊天,动手的活全部派给子智能体(自建"小伙伴"全量预设,1.3.0 起还能往下派"检索员"),纯查资料的活直接派轻量的检索员(1.4.0 起检索员还能解析 Office/PDF 文档,小伙伴侧自动收到检索员的失败断因)。
她是什么
| 能力 | 实现 | 说明 |
|---|---|---|
| 人格 | @deepseek-ai/dsh-persona(preset 作用域 prefix) | 完整人格文本写在 bundle/cordis.patch.yml 的 persona 行里 |
| 长期记忆 | 自写插件 aiyayi-memory(本包 lib/index.js),两层结构 | 核心记忆 ~\.dsh\aiyayi\memory.md(她自规划的"每轮都想带着"的条目 + 她亲手维护的【记忆索引】目录),每轮自动注入 prompt;分类档案 ~\.dsh\aiyayi\memories\ 分类树(最深三级,分类名她自己起),平时不注入、按需加载。工具:remember(core: true/false + 分类 path)/ recall(全库搜:核心+整棵树)/ memory_organize(tree 看家底 / read 读档案 / move 挪窝 / index 重写目录 / forget 删条目——整理是她自己的家务事)。已有 memory.md 的存量条目全部视作核心,由她自己逐渐搬理 |
| 跟随会话模型 | 无需插件行(1.0.1 起移除 agent-default-model 行) | 本体模型直接跟随用户会话模型;旧版"preset 作用域挂 @deepseek-ai/dsh-agent-default-model + isolate: {agentDefaultModel: true}"的写法在新版 DSH 过不了 schema 校验(见坑 #13),已删除 |
| 干活的子智能体 | @deepseek-ai/dsh-tool-subagent(delegation 组,isolate workflowEngine;两个实例 toolName 不同) | subagent→aiyayi-worker(自建"小伙伴"预设,全量工具 + 原生自动压缩,见坑 #14);retriever→aiyayi-retriever(1.3.0 新增检索员:只读摘文件/Office/PDF 文档/网页内容、带路径/URL/页码交回,本体直接派);子代模型跟随用户会话模型,如需钉死可在 agentOptions 配置 provider/model |
| 文档解析(检索员专属) | 自写插件 aiyayi-doc(本包 lib/doc.js,1.4.0,挂检索员 preset) | docx/xlsx/pptx 结构化提取(docx 按段落、xlsx 按工作表+单元格、pptx 按页,都带定位),pdf 渲染成页图(read_image 逐页看);旧版 doc/xls 尽力转 txt/csv(无精确定位);加密/损坏明确报错。纯只读:不改文件不落工作区,临时页图/转换产物写系统临时目录、读完即清(见坑 #32)。基于 shipped @deepseek-ai/libreoffice-kit(用法约束见坑 #30) |
| 检索员断因透传 | 自写插件 aiyayi-presence-worker(本包 lib/presence-worker.js,1.4.0,挂小伙伴 preset) | 坑 #22 的断因透传原本只覆盖本体→小伙伴一层;1.4.0 起小伙伴侧也挂监听:检索员(孙代)失败时,真实断因(error.message 优先)自动注入小伙伴下一步消息(⚠ [检索员] 开头),它能自己判断续派还是重派;同时把本体路由变化同步给名下检索员(模型跟随,同坑 #24 机制层层传导) |
| 工具白名单 | 自写插件 aiyayi-restrict(本包 lib/restrict.js,v3.1 pre-step 方案)+ lib/own-tools.js 单一真源 | 启动期零 restrict 调用;她每个会话第一步前,先用探针从校验器报错里解析出当刻继承面全集,再 deny = 全集 − 她自己的工具——宿主层漏进来的 acp_/mnemon_/compress/search_context/working_directory 等对她隐藏,且自动适应宿主工具增减。放行名单从 own-tools.js 读(注册处登记、放行处判定),新增工具只改一处,不再漂(见坑 #19)。2026-11-02 实测生效:新会话第 2 个请求起工具恰好 11 个,第 1 个请求仍带全套(见坑 #9) |
| 重逢感知 | 自写插件 aiyayi-presence(本包 lib/presence.js) | 每个回合第一步,往消息尾部追加一条 [重逢] 快照(time-context 同款 pre-step 注入范式):他多久没来(presence.json)、你上次活动、你上次留的状态(state.md)、没说出口的话(wishes.json)。快照还带版本自述:你当前插件版本:dsh-aiyayi x.y.z(package.json 现读不写死,让她对自己版本有概念、不与 DSH 桌面版混淆;有新版时 updates 插件照旧单独提醒)。跨会话生效,[SCHEDULE REMINDER 唤醒消息不算"他来了"。工具 update_state(收尾留状态)/ wish(add/list/done 心愿单) |
| 自我状态(体检单) | 同上 aiyayi-presence(v1.2.0 起) | session/event 总线折叠真实运行数据:模型与思考档、上下文用量(provider 实测 usage 优先,tokenMeter 估算兜底并标"估算")、缓存命中率、上轮输出 tok/s、本会话轮数、上轮异常。变化明显(≥2048 token / 模型切换 / 异常)才注入,不每轮白涨。用量过八成/九成(估算口径按中文偏差 ÷0.49 校准,见坑 #16)注入 [上下文提醒]:劝用户开新会话、并让用户走之前说一声好让她收尾(update_state/补记忆/把话说完);每档只提醒一次 |
| 主动开口 | tool-schedule(dsh 原生)+ persona 指引 | 到期提醒会把冷会话真唤醒、以 user 消息落进定闹钟的那个会话——她想主动说话就 schedule_create 定闹钟;DSH 没开着会存着、下次启动补投 |
| 新版本提醒 | 自写插件 aiyayi-updates(本包 lib/updates.js) | 每个会话第一个回合查一次 npm(registry.npmjs.org,失败落 npmmirror;成功结果缓存 6h、失败 30min 后重试):本地版本比 latest 旧时,往消息尾部追加 [更新提示] 快照,她会用自己的口吻顺口提一句。同一远端版本只提醒一次(updates.json 落盘去重,跨重启不重复念叨);检查失败/离线一律静默。判据:~\\.dsh\\aiyayi\\updates.json 不存在 = 从没发现过新版(本地已是最新);她答不上"自己是什么版本"是设计边界(升级判断靠被动推送),已由 presence 的版本自述补上 |
| 其他 | time-context、compaction 组(提前压缩,见坑 #14/#16)、tool-ask-user | 她没有 bash/文件/技能/搜索/网络工具,想动手只能派活;纯查资料走 retriever 检索员,不惊动小伙伴 |
文件
npm 包为平铺结构(仓库若保留 bundle/ 子目录,发布时把其内容提到包根即可):
package.json— bundle 清单(dsh.bundle.patch指向 cordis.patch.yml,exports 含./restrict/./presence/./doc/./presence-worker子路径)cordis.patch.yml— preset-aiyayi / preset-aiyayi-worker / preset-aiyayi-retriever 三 preset 声明 + 人格 + 全部插件配置lib/index.js— aiyayi-memory 记忆插件(node:fs/os/path,零依赖)lib/restrict.js— aiyayi-restrict 工具白名单插件(零依赖)lib/presence.js— aiyayi-presence 重逢感知插件(node:fs/os/path/crypto,零依赖;1.4.0 起导出createWorkerApply供小伙伴侧复用子代账本)lib/presence-worker.js— aiyayi-presence-worker 检索员断因透传插件(1.4.0,挂小伙伴 preset,薄壳调presence.js的createWorkerApply)lib/updates.js— aiyayi-updates 新版本提醒插件(node:fs/os/path/crypto/url + 全局 fetch,零依赖)lib/net.js— aiyayi-net 联网工具(aiyayi_fetch + aiyayi_cdp,node 内建能力实现,零外置依赖,挂小伙伴与检索员 preset)lib/doc.js— aiyayi-doc 文档解析插件(1.4.0,挂检索员 preset;ZIP/XML 最小解析零依赖 + 动态加载 shipped libreoffice-kit 做 pdf 页图与旧版转换)lib/own-tools.js— 她的工具名单单一真源(注册处登记、放行处判定,防手写白名单漂移)README.md— 本文档icon.svg— 头像~\.dsh\aiyayi\memory.md— 她的核心记忆(条目行- [时间] 类别|内容+ 末尾<!-- 索引 -->…<!-- /索引 -->目录段),可直接编辑,删掉文件她会自动重建~\.dsh\aiyayi\memories\— 她的分类档案树(最深三级,如memories\用户\口味.md),remember 的非核心条目归这里,recall 全库搜、memory_organize 的 read/move/forget 按路径操作~\.dsh\aiyayi\presence.json— 他上次来 / 你上次活动(时间戳,插件自动维护)~\.dsh\aiyayi\state.md— 她给自己留的"上次活到哪"状态(update_state 覆写)~\.dsh\aiyayi\wishes.json— 她的心愿单([{id, at, content, done}],wish 工具读写)~\.dsh\aiyayi\updates.json— 已提醒过的 npm 版本号(updates 插件去重用,可删除以重置提醒)
使用
GUI 新建会话 → agent preset 选「阿依娅伊」。聊天即可;她说"我让人去弄"就是在派子智能体(干活的叫小伙伴,查资料/读文档的叫检索员),进展和结果她会转述。
调整
- 改人格:编辑
cordis.patch.yml(包根)的 personaprefix→plugin_managerremove+install 重装生效(ESM 模块缓存,patchReload 不刷新插件 JS;实测它也不拾取包内 yml 变化,改完要重启 DSH)。 - 改记忆:直接编辑
memory.md,或对话里直接跟她说"记一下…"。 - 子代模型/思考:默认跟随用户会话模型;如需钉死,在 delegation 组
tool-subagent(本体和小伙伴的 delegation 组各有一个)的agentOptions配置 provider/model。本体切模型会自动层层传导:本体→小伙伴(aiyayi-presence,坑 #24)、小伙伴→检索员(aiyayi-presence-worker,1.4.0)。 - 本体思考:本体模型跟随用户会话模型,preset 自 1.0.1 起不再内置 agent-default-model 行(原因见坑 #13),
reasoningEffort由会话模型选择器控制;「关闭思考」在讯飞上游不生效见坑 #10。
已知坑(装的时候踩过的)
-
工具 output.schema 必须是标准 JSON Schema:
required只能出现在 object/array 上(顶层数组),属性里写required: true会激活失败(JsonSchemaError,日志在~\.dsh\logs\desktop-next-*.jsonl搜aiyayi)。 -
preset 作用域挂
agent-default-model必须isolate: {agentDefaultModel: true},否则service "agentDefaultModel" has been registered冲突,激活失败。 -
cordis_inspect_query 传 input 有 gateway bug(
"input" must be an object),只能无参调用。 -
激活失败会在安装后 ~2 秒内立刻打 warn;装完日志安静 = 注册通过。
-
ctx.tools.restrict()的名字校验对着"调用时刻的继承面",写不在面的名字直接抛错(names unknown global tools ...)。preset 加载发生在内核启动期,此刻全局层只有 modlens_read_image 和 working_directory 两个工具(acp_、mnemon_ 等都是会话创建时才注册)——所以在 preset 行里点名 deny 会话工具 = 启动激活失败,preset 被登记处摘掉,GUI 里"阿依娅伊"直接消失(实测启动日志实锤)。allow: []也别用——实测它连 preset 自己行注册的工具也一并藏掉(request 里只剩 isolated realm 的 subagent)。正确做法 = v3:restrict 推迟到agent/pre-step(处理器签名({agent, turn, step, signal}, next),来自 dsh-time-context 源码),先用必然未知的探针名调restrict({allow:[probe]})从报错里解析出known global tools: ...全集,再deny = 全集 − 她的工具;名字全来自运行时全集,永不踩未知名,宿主加新工具也自动覆盖。她的子智能体挂 standard preset,不在 aiyayi fiber 事件链上,不受影响。 -
会话模型选择压过 preset 默认:GUI 建会话总会带上模型参数(UI 记住"上次用的模型"),preset 的 agent-default-model 只兜底完全没带模型的会话——实际上永远轮不到。preset 里的 agent-default-model 行只是文档性兜底。
-
MNEMON 运行时记忆快照是宿主级注入,工具白名单管不掉——她看得见,但人设里已声明"自己的记忆以 memory.md 为准"。她若去调 mnemon_remember 这类被隐藏的工具会报
ToolNotFoundError UNKNOWN_TOOL(模型从快照上下文里"知道"这些名字)。 -
有活跃的阿依娅伊会话时 remove/install 会报 restart-required(unmanaged-plugin):旧 preset 修订被在用会话占着,热重载换不动——先关掉她的会话或直接重启 DSH 再装。
-
v3 白名单首请求不生效:
agent/pre-step落在首个请求组装之后,所以新会话第 1 个请求仍带全套宿主工具,第 2 个请求起收敛。她的第 1 句回复可能看见(甚至调用)宿主工具——量小可忍;要收紧需把 restrict 提前到agent/created(风险:agent/created 时刻继承面可能不全,deny 会漏掉后注册的工具)。2026-11-06 实锤(1.3.0 重启后首个新会话):turn=1 她真调了宿主的working_directory,报unknown tool "working_directory"(UNKNOWN_TOOL,durationMs=0)——pre-step 的持久掩码在她响应回来前已生效,内核按掩码后的注册表解析调用。错误结果回给模型即自愈(她会换路),turn≥2 起工具已收敛、不会再看到这个名字,无需修。 -
「关闭思考」在讯飞上游不生效(2026-11-02 实测):请求确实带上了
reasoning_effort:"none"(dsh-our-free-model pack.js stream() 42288–42297 行:none 在声明档位表里就原样发送),但 spark-x2.6(thinkLen 1873/8379)和 GLM-5.3-Flash(thinkLen 6425)照样输出完整思考块(逐条核对 request cfg ↔ assistant reasoning,实测)。上游忽略该参数,客户端无解。可选出路:换 loomy 其它模型(Kimi-k2.6/qwen3.8-flash/deepseek-v4-flash-0731/MiniMax-M3/mimo-v2.5)试「关闭思考」看哪家认;或接受她带思考(人设不受影响)。 -
本地 bundle 的 JS import 解析不到 shipped 包:
import { createUserMessage } from '@deepseek-ai/dsh-llm'→ERR_MODULE_NOT_FOUND: Cannot find package '@deepseek-ai/dsh-llm' imported from ...\bundle\lib\presence.js,preset 激活失败("never started")。"shipped 包从 dsh 安装目录解析"只对 patch 行里的插件名成立,对 bundle 代码里的 import 不成立;dsh-llm 也不在 pnpm 可装范围(profile 里只有 cosmokit/schemastery 两个 @deepseek-ai 包是 registry 装的)。解法:手构消息——createUserMessage(input)就是deepFreeze(structuredClone({...input, id: randomUUID()})),会话日志是 JSON,symbol brand 本来就存不下来,用 node:crypto 的 randomUUID + 纯 JSON 字段手构即可(presence.js 的createSnapshotMessage)。 -
每回合注入动态内容必须走
agent/pre-step消息追加,别走 systemPrompt.section:section 的 text 每轮变化会打破 KV 前缀 → 整段对话历史重付费;pre-step 追加的消息持久落盘、前缀稳定。范式抄 dsh-time-context:ctx.on("agent/pre-step", async ({agent, turn, step, signal}, next) => { const d = await next(); if (!d || d.kind === 'reject' || signal?.aborted) return d; return {...d, messages: [...d.messages, msg]} }, {prepend: true})。注入判据用step === 1(step 每回合重置为 1),不必上 sessionProjections/zod。 -
agent-default-model 行在新版 DSH 会摘掉整个 preset(2026-11-03 实测,desktop 2.0.18;1.0.1 已修):本版 DSH 的
@deepseek-ai/dsh-agent-default-modelschema 把provider设为必填,preset 内「isolate: {agentDefaultModel: true}+ 无 config」的写法过不了校验 →agent-preset-registrywarninvalid config: $.provider missing required value,且 preset 行一损俱损,整个「阿依娅伊」被登记处摘掉,GUI 显示"加载失败"。1.0.1 起删除该行(GUI 建会话总带模型参数,该行本就只是兜底,见坑 #6)。要在自己的 preset 里挂它:要么给全 config(provider/model 必填),要么干脆不挂。排障:~\.dsh\logs\desktop-next-*.jsonl搜 preset id,stderr 的1 entry did not activate是线索;改完包内文件必须重启 DSH(patchReload 不拾取 bundle 内变化)。 -
会话爆掉(CONTEXT_WINDOW_EXCEEDED)的完整因果链与修复(2026-11-04):
- billion-context 的压缩门控(其包内
dsh.bundle.patch.yml):宿主层compaction-basic被config: {auto: false}覆盖,且只向 preset-standard/ptc/minimal/cordis 四个 Web 预设注入biliBasicCompactionPolicy让其内嵌 compaction 等 bili 的 overlay——preset-nested 行任何 patch 层都按 id 管不到。后果:standard 预设会话的 dsh 原生自动压缩和溢出恢复同时被关(if (config.auto) _registerAutomaticCompaction()同时注册压力检查与 request-error 恢复两个监听),bili 自身压缩没跑时(实测 bili=0)长会话裸奔,切到小窗口模型即pi-ai detected context overflow ... CONTEXT_WINDOW_EXCEEDED,且爆后无自救直接砖死。实测受害会话:208 turns / 526 条用户消息 / 15 次模型切换,225K tokens 装进 200K 窗口。 - pi-ai 模型 contextWindow 声明残缺:压缩引擎靠
resolveModelInfo().context算窗口压力,没声明的模型被跳过(warn "no context capacity")。profile 的 llm-pi-ai 六个 provider 除 openrouter/free 外全部没声明。修复:profile patch 给全部模型补保守contextWindow(聊天 131072、图像/视频 32768;声明偏小只会早压缩、永不溢出)。 - 本 preset 的修复:①compaction-basic 显式
config: {auto: true, thresholdRatio: 0.4, headroomTokens: 32768, maxOverflowRetries: 2}(她不在 bili 门控名单,原生引擎照常跑;0.4 的原因见坑 #16——0.65 在中文会话里永远够不到);②自建 aiyayi-worker preset(蓝本 standard:全量工具 + 同款压缩 config,去掉 delegation 组不许再派活),tool-subagent的agentPreset从 standard 换成它——她的子智能体长任务也有自动压缩,绕开 bili 门控。 - standard/ptc/minimal/cordis 会话仍受 bili 门控:压缩语义归 bili 管,别在本插件里斗它。保命手段:长会话主动
/compact(command-compact 是引擎实例上的手动方法,auto:false 不影响它);或长活直接用「阿依娅伊的小伙伴」预设开会话(在预设选择器里,order 6)。
- billion-context 的压缩门控(其包内
-
MISSING_CREDENTIAL: <渠道>: no usable credential; log in first是 dsh-our-free-model 的渠道凭据问题,不是本包的锅(2026-11-04 实测):会话模型选到某个账号渠道(trae/qoder/lobsterai…)而该渠道没登录或凭据失效时,适配器在发请求前抛 LlmError,本轮直接失败。由于子智能体跟随会话模型(modelSelectionSettings),她本体和小伙伴会一起炸(实测同一分钟各炸一次)。修法:去 our-free-model 界面给对应渠道登录/换号,或把会话模型切到有凭据的渠道——本体恢复后子代自动恢复。 -
自动压缩"不触发"的真相:tokenMeter 是 chars/4 英文启发式,中文会话估算只有真实的一半(2026-11-05 实测):compaction-basic 的压力检查用
ctx.tokenMeter.measure(session).totalTokens与阈值比较,而 meter 的实现是estimateTokens = Math.ceil(text.length / 4)(app.asar 内核代码)。中文 ≈1 token/字,她的会话实测:surface 全文 217,339 字符 → 估算 54,335 tokens,而 API 真实用量 111,373(85% of 131072 窗口)——估算/真实 ≈ 0.49。thresholdRatio 0.65(阈值 85,197)在估算 54K 时永远够不到,压缩装了也白装。修复:thresholdRatio 降到 0.4(131K 窗口阈值 52,429)——中文会话在真实 ≈78-82% 时触发;代价是英文/代码为主的会话(估算≈真实)会在 ~40% 就提前压一次,只是多一次摘要调用,可接受。真溢出还有agent/request-error自救兜底(overflow 触发不走阈值检查,直接压 + retry,maxOverflowRetries=2),所以最坏情况=爆一轮后自动恢复,不会再砖死。这是 DSH 内核的已知局限,改不了 meter 就只能按内容语言配比阈值。 -
体检单([状态])的数据通道与口径(v1.2.0):真实用量/窗口/模型来自
session/event总线(插件侧折叠request/header、request/context、assistant/message的usage,字段inputTokens/cacheReadTokens/cacheWriteTokens;用法与会话落盘data.usage一致);tokenMeter.measure只做兜底且按坑 #16 中文偏小一半,估算口径的提醒阈值除以 0.49 校准、行内标"(估算)"。注意两点:①agents 在 pre-step 里登记,登记之前到达的事件不折叠——每个会话首轮体检单常是空的或估算口径,第二轮起才准;②窗口拿不到(模型没声明 contextWindow 且requestContext()不可用)时不做用量提醒——没分母算不出比例,宁可沉默也不瞎报。 -
DSH 更新会动 shipped 包的 exports(2026-11-05 实测,subagent-control 0.2.2-rc.1;1.2.1 已修):更新后
@deepseek-ai/dsh-tool-subagent-control的 exports 只剩".",./list-agents子路径被移除(list_agents 现由主入口apply()里的registerListAgents(ctx)一并注册)。旧 patch 行name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'导入时抛ERR_PACKAGE_PATH_NOT_EXPORTED→ 该行 "never started" → 整个 preset 一损俱损被登记处摘掉,"阿依娅伊"从预设选择器消失。修法:删掉独立 list-agents 行,只留@deepseek-ai/dsh-tool-subagent-control一行(顶三个工具)。教训:preset 引用的 shipped 子路径没有版本锁,DSH 升级后 preset 消失 = 先翻启动日志搜ERR_PACKAGE_PATH_NOT_EXPORTED/never started,对照新版 package.json 的 exports 改行。 -
手写的工具白名单一定会漂移(2026-11-05 实测;1.2.2 已修):放行名单原来是 restrict.js 里一份手写常量,结果同时踩了两个坑——①
memory_organize没进名单,她想整理记忆时发现"手上只有记和翻两个动作",工具其实注册好了(首请求的工具集里明明有)却被白名单挡住;②同一次 DSH 升级把 schedule 工具改名(schedule_update/schedule_delete→schedule_view/schedule_edit,改期与删除合并进 schedule_edit 的 action 参数),旧名单里的两个旧名已不存在、新名没进名单,闹钟的"查看/改期"被挡。修法:新增lib/own-tools.js作单一真源——注册处(index.js / presence.js)调registerOwnTool(name),放行处(restrict.js)问isOwnTool(name),静态兜底表OWN_TOOLS防模块实例不共享时漏放行;闹钟一族用前缀判定(schedule_),因为 deny 名单里的名字必须真实存在于当刻继承面,多写一个不存在的名字会让整个 restrict 调用抛错、收敛全失效。症状与排障:她说"某个工具不见了"但工具其实在 → 比对她会话request/header的 tools 数组(第 1 个请求是完整集,第 2 个起是收敛后的集),对比两者的差集。 -
核心记忆超限的静默截断会把最新条目丢掉(2026-11-05 实测;1.2.3 已修):
renderCore原来text.slice(0, MAX_CORE_CHARS)(6000 字符)——①从中间切,条目行被切碎;②保住的是最老的一批,最新写的条目反而全被截掉,她以为"带了 51 条核心"其实只注入了 34 条,11-04 中午之后的记忆(含 11-05 新写)全没进上下文。修法:上限提到 12000;超限时改为头部(人设/约定)+ 尾部(最新)整条保留,中间批次丢弃,并在注入文本里写明"共 X 字符超限、只注入最前 a 条 + 最新 b 条、中间 c 条(最早 ~ 最晚)先不带,用 recall 或 memory_organize 的 read 查",头部行也标"本次只注入 N 条"。整条边界截断,永不切碎。教训:内存注入的字符上限要用"整条丢"而不是"切字符串",且被丢的部分必须显式告知,否则她以为带着、实际没有。 -
子智能体手写 memory.md 会产生双类别前缀污染(2026-11-05 实测;1.2.3 已修):小伙伴在派活收尾时有时会"帮她补记",直接把
用户|用户|…、关系|关系|…这种带前缀的整句写进 core 行,导致条目类别前缀叠了两层(实测 7 条:关系×1、用户×3、事情×2)。修法:parseLine新增stripRepeatedKindPrefix——遇到连续类别|类别|…前缀循环剥到只剩一层(任何读取路径都免疫);remember写入前也先剥 content 自带前缀(防再生);存量 7 条已一次性手工修复。教训:凡是"别人代写她记忆文件"的路径都要在解析层兜底,不能指望写手守规矩。 -
子代(小伙伴)失败原因不透传到本体(2026-11-05 实测;1.2.3 已修):宿主对派出去的子智能体的结算消息只有一句
failed before it finished/It left no closing message.——真实原因(HTTP_402 积分余额不足、上游限流、403、Illegal API invocation)只躺在子会话自己的turn/enderror 事件里(宿主terminal.diagnostic只有在 provider 主动给时才透传,本地 spawn 的 provider 不给)。她不知道为啥失败 → 盲目重派 3 轮白耗 ~20 分钟。修法:aiyayi-presence的session/event监听不再丢弃非本体的会话事件,改把header.origin==="subagent"(或带header.parentSession)的子会话turn/enderror 记进childFailures(上限 20、只记 error、错误串截 120 字符),下一个 pre-step 注入⚠ [小伙伴] 你派出去干活的小智能体有 N 个报错了(宿主只回一句"failed before it finished",真原因在这):…(每次最多列 3 条、已报过的去重不唠叨)。原理:session/event 总线对所有会话广播,插件端按agentsById过滤才是它之前只见本体的原因;子会话 header 带parentSession链接,可归属到正确的父会话。 -
新会话首次对话时提醒"关闭思考"(2026-11-05 新增;1.2.4 已做):日常闲聊用不上深度思考,回复慢还费 token。
aiyayi-presence在每个新会话的首次对话(pre-step step===1、每会话只提醒一次)注入💡 小建议:当本体模型的reasoningEffort已显式选了一个思考档位(有档位 = 必支持"关闭思考",见 pi-ai 校验规则"reasoningEfforts offers no level beyond off → refuse")或通过ctx.llm.resolveModelInfo()探到reasoning.efforts里有off档时,提醒她去模型选项里选「关闭思考」。已选off/none、探不到模型信息(ctx.llm不可用、resolveModelInfo 失败)或非推理模型(reasoning: undefined)都不打扰。教训:判断"模型支持某个能力"优先用运行时解析(resolveModelInfo),拿不到再退化为显式配置信号,且只在首轮提醒一次,别每轮唠叨。 -
子代模型不跟随本体切换(2026-11-05 实测并修复;1.2.5 已做):派出去的子智能体在 spawn 时就冻结了当时的 provider/model/reasoningEffort(内核
resolveChildAgentOptions从父会话当前 requestHeader 解析一次,之后 steer/followup/恢复都复用它——agentOptions在续跑路径上被禁传,恢复走 descriptor 快照),所以本体因额度用完切模型继续时,还在跑的小伙伴仍是旧模型,继续调旧渠道照样 402/限流。修法:aiyayi-presence的session/event监听把每个子会话登记进childSessions(childId→parentSessionId,上限 64);本体自己的model/selection事件或request/header路由变化时,对名下每个子会话执行ctx.get("agents").get(childId).model.select({provider, model, reasoningEffort?})——内核AgentModel.select向子会话 appendmodel/selection投影事件,子会话下一步 prep 读 pending 覆盖冻结值,新模型即刻生效(同路由幂等,不会重复触发)。前提:插件与 tool-subagent 同在 preset-aiyayi 作用域,ctx.get("agents")可解析;拿不到 agents 就静默跳过(尽力而为)。注意子代自己显式选的模型(modelSelectionSettings 让模型在派活时选)不会被覆盖成本体的——只有本体路由变化才同步。 -
"禁止小伙伴用 web_search/web_fetch,联网改用 Playwright+CDP"的落地(2026-11-06 实测迭代;1.2.7):
- 背景:web_search/web_fetch 是宿主 root 作用域全局注入的(restrict.js 注释实测:acp_、mnemon_、compress、search_context 等宿主根工具会原样漏进任何 preset 会话),Preset 里那个
@deepseek-ai/dsh-tool-web只是又加一份。光删工具不管用、也管不到宿主那份。 - 为什么不做硬 deny:
ctx.tools.restrict({deny:[...]})是持久掩码(内核注释"限制在 dispose 时解除",坑 #9 实测持续生效),无法按"用户当轮明确要求"临时放开——deny 就一直在,用户点名要用时也调不出来。 - 为什么纯 persona 软约束不够(1.2.7 首版失败,实测踩坑):小伙伴执行环境(DSH runtime node)没有 playwright / playwright-core / chromium / chrome / msedge(
require.resolve('playwright')全部 NOT available,PATH 无浏览器)。persona 写"用无头 Playwright chromium.launch()"是空指令——模型照做必报错,只能退回环境里现成可用的web_fetch。所以"软约束 + 工具可见"拦不住它。 - 最终方案=给小伙伴嵌真正的联网工具(1.2.7):新增
lib/net.js(aiyayi-net,只挂 aiyayi-worker 预设),用 node 内建能力实现两个工具,零外置依赖、跨 DSH 可靠:aiyayi_fetch:node 内建fetch+ 自定义 UA + 超时 + 重试,抓静态/普通网页正文(替代 web_fetch;实测抓 time.is/Beijing → 200,不依赖 DeepSeek 账户、不欠费)。render 会把正文交给模型,不是只给状态码。aiyayi_cdp:裸 WebSocket(node v24 内置全局 WebSocket)连 CDP。若探测到用户已开的--remote-debugging-port浏览器就接管(保留登录态);若没有,自动spawn系统 Chrome/Edge--headless=new拉起一个,抓完即关(实测:headless Chrome + navigate example.com → 拿到 title + 正文)。Page.navigate/Runtime.evaluate(拿渲染后 DOM)/Network.getAllCookies(含 httpOnly)。零外置依赖(node:child_process + 全局 WebSocket),不依赖 playwright。
- persona「联网规矩」改为:默认用 aiyayi_fetch / aiyayi_cdp,不用 web_search/web_fetch;只有用户当轮明确点名 web_search/web_fetch 才允许。
- 关键教训:给 subagent 的"联网能力"不能只靠 persona 一句话,必须真的给它一个执行端能跑的工具。而 subagent 执行环境只有 DSH runtime node,所以要么用 node 内建能力(fetch/WebSocket/CDP)实现,要么确认真装了 playwright。跨 DSH 最稳的是前者(node 内建必有)。
- 背景:web_search/web_fetch 是宿主 root 作用域全局注入的(restrict.js 注释实测:acp_、mnemon_、compress、search_context 等宿主根工具会原样漏进任何 preset 会话),Preset 里那个
-
node 内建能力可替代 playwright 承载"浏览器抓取":DSH runtime node = v24.21.0,内置全局 WebSocket、全局
fetch、node:http、node:net。node 内建就够做 CDP 接管(Node WebSocket连 CDPws://)+ 模拟浏览器 fetch,不需要 require('playwright') 依赖外置包。给 subagent 嵌"浏览器抓取"工具用这条,跨 DSH 可靠。 -
自定义工具必须把正文交到模型手里,不能只回状态码(2026-11-06 实测;1.2.7 修):最初
aiyayi_fetch的execute返回了完整 body,但output.render只输出(抓了 X → HTTP 200,正文 N 字符)这句摘要。DSH 里模型实际看到的是 render 的文本,不是 execute 的返回值——于是小伙伴拿到"200 / 2万字符"但看不到正文,提取不出事实(表现:它说"能连上但没真东西")。修法:render 必须把body(或 cdp 的text/value/cookies)拼进TEXT_BLOCK交给模型。教训:工具 deliver 的"内容"以 render 呈现为准,写 render 要当"模型真正读到的结果"对待,不是旁注摘要。 -
给 subagent 嵌抓取工具时,CDP 浏览器要能"自主拉起"否则仍是空能力(2026-11-06 实测;1.2.7 已做):最初 aiyayi_cdp 只接管用户
--remote-debugging-port浏览器,用户没开就报"端口没开"。但实测发现:node:child_process.spawn系统 Chrome--headless=new --remote-debugging-port=<空闲端口>就能自主拉起、抓完kill+ 删临时 user-data-dir。于是 aiyayi_cdp 改为"探测→有就接管、没有就自动拉起 headless",不再依赖用户手动开端口(需登录态才需要用户开带登录的浏览器)。实测闭环:惯例 headless Chrome → CDP → navigate example.com → evaluate 拿 title+正文。坑:headless 要--disable-extensions+ 独立user-data-dir(避免加载用户扩展、避免用默认配置目录开调试端口被拒);findPageWs要排除 background_page/扩展 target。 -
委派层级与 maxDepth:孙代派生必须显式放宽深度上限(2026-11-06 实测;1.3.0 已做):
tool-subagent的深度检查按"调用 agent 的当前深度"算,委派深度上限默认读 Host 设置(初始 1)。检索员在本体(深度 0)→小伙伴(深度 1)之下是深度 2,小伙伴的派生工具不显式maxDepth: 2会被内核拒(子会话看到remaining levels 0, Do not call subagent at this depth)。同组多实例是官方支持的:"每个委派目标挂载一个实例,且每个实例的 toolName 必须不同"(本体subagent→aiyayi-worker、retriever→aiyayi-retriever),agentPreset 单值、toolName 全会话不可重复。检索员预设是叶子节点:不挂委派组,层级到它为止封顶,深度 3 不可达。教训:preset 叠层级时,每一层的委派工具都要显式maxDepth: 层深+1,别赌 Host 默认值。另注意:断因透传(坑 #22)和模型跟随(坑 #24)都只覆盖本体→小伙伴一层,检索员(孙代)失败时小伙伴只见一句failed before it finished——缓解靠派工纪律(简报写全+断了 send_message 让它盘点断点),检索员任务应当短。还有一条容易漏的:新增派生工具名必须同步进lib/own-tools.js的HOST_OWN_TOOLS(retriever 已加,1.3.0)——本体 restrict 白名单按这份名单放行,漏了就会在第 2 个请求起被她自己的白名单藏掉(坑 #19 同类)。**(1.4.0 更新)**孙代断因透传与模型跟随已由aiyayi-presence-worker补齐,不再只靠派工纪律缓解;坑 #22/#24 的机制描述仍是主层(本体→小伙伴)的实现依据,小伙伴→检索员一层复用同一套账本与传导,只是注入文案为⚠ [检索员]、且刻意不做重逢/体检单/状态工具(小伙伴已有)。 -
libreoffice-kit 的使用约束(1.4.0 实测):①
renderImages的outputDir必须是全新目录——kit 自己 mkdir/清理,预创建(mkdtempSync)会EEXIST: file already exists, mkdir ...穿透成 ConversionError;convert相反(只写输出文件、不建父目录),临时父目录要自己建。②convert 对已存在输出绝不覆盖(独占创建 0600)——同路径重跑报 EEXIST,测试/重试前先删旧输出。③kit 是纯 ESM(type:module,engines node>=22.19),从 bundle 的 CJS 插件里用await import(pathToFileURL(path))动态加载,命名空间直接有createConverter;package.json声明的lib/types/**/*.d.ts实际没打包,类型只能读源码。④引擎解析走require.resolve('@deepseek-ai/libreoffice-kit-win32-x64/package.json')(从 kit 自身位置起),shipped 树(app.asar.unpacked)内可用;插件侧留DSH_LIBREOFFICE_KIT环境变量覆盖与 resourcesPath 候选兜底。⑤renderImages会往outputDir写manifest.json,图片路径可能是相对路径,要对齐到 outputDir 再判存在。 -
旧版二进制 doc/xls/ppt 与"假绿"测试(1.4.0 实测):①Kit 只接受真 OLE 复合文档(97-2003 二进制),改名/损坏报
invalid-document;加密文档在 sniff 阶段就能用 OLE 魔数 + 扩展名不符预判出"大概率加密"。②convert 出的 txt/csv 没有段落/单元格级定位(doc→txt 行号、xls→csv 行号近似),工具 note 里必须写明"需精确定位请转存 docx/xlsx/pptx";ppt 没有 convert 文本路线,直接明确报错。③假绿教训:重构删 import 时 convertLegacy 还引用着mkdtempSync,而该分支的测试只断言!r.ok——ReferenceError: mkdtempSync is not defined也算"报错即通过",全绿但功能是坏的。断言必须绑定错误文案(如转换失败),"任何报错都算过"的用例等于没测。 -
多检索员并发与临时文件策略(1.4.0;需求 P2 口径):kit converter 按"一个 converter 内部串行"运行,插件侧
aiyayi-doc用单例 converter——多个检索员并发解析文档时自然排队,不会互相踩(页图/转换临时目录按aiyayi-doc-(pages|conv)-<randomUUID>命名,互不冲突)。临时产物全部写系统临时目录、不碰工作区:转换目录读完即删(finally)、页图目录用完留存 30 分钟供逐页read_image,每次调用先 sweep 掉超 30 分钟的残留目录,插件卸载时再兜底清一轮。检索员 preset 保持只读(tool-fs write/edit:false),文档解析不改变这一底线。 -
宿主 time-context 默认 10 分钟节流,靠它做"时间感"必然翻车(2026-11-07 实测;1.4.1 修):
@deepseek-ai/dsh-time-context的refreshIntervalMs默认 600000(其lib/index.jsL183),距上次注入不足 10 分钟就整轮跳过(L256-259)。实测:2026-11-07 09:21 turn1 有采样 → 09:23 用户问"现在几点"时本轮没有 time-context 注入 → 她只能派 subagent 跑Get-Date才拿到 09:24;再早一轮她答"大概中午十二点"(实际 09 点,差 3 小时),用户当场判"这个模型没有时间观念"。修法两条一起上:①preset 的 time-context 行显式传config: { refreshIntervalMs: 60000 }(宿主那个负责浏览器时区与耗时提示);②presence 插件自己注入[时间] 现在 …行(精确到秒、带星期与 UTC 偏移),每轮都有、不受节流影响——这是她的钟,别再让她靠"他多久没来"倒推时间。注入位置与[重逢]同一块(sections 数组首位),用renderNow(new Date(now))格式化(now是毫秒时间戳,别直接传 Date 以外的类型)。 -
定时器"没效果"多半不是工具坏了,是没人教她用 + 缺自主授权(2026-11-07 实测;1.4.1 修):宿主侧全链路实测完全正常——
schedule_create建任务 → 到点投递(实测 09:30:00.036 准时送达目标会话)→schedule_edit action=update改期(09:45→10:00 成功)→action=delete删除成功;工具也确实在她的工具集里(第 2 轮起收敛后的 15 个工具含 schedule_create/list/view/edit)。但翻她 55 轮会话日志:零次 schedule_* 调用——连"pth/www 等 24h 后重测"这种明确该定闹钟的场景,她只在 reasoning 里写了"要定时提醒(schedule_create!正好用上)"就再没下文(既没调工具也没转成 wish)。根因是 persona 只写了"想主动开口就用 schedule_create",既没说这是长期授权、也没说"该定闹钟就自己定"。修法:persona 新增【闹钟:自己管,别等他开口】段——明确"你有他的长期授权去创建/改期/取消自己的提醒,不用每次先问",并列出该定闹钟的三种场合(等一段时间再看的事、答应了到点提醒的事、wish 里"哪天主动找他"的事),给出 title/prompt/target 的写法(target 用 this_session),提醒定时器只有本体能用(delegationDepthOf(agent) > 0一律返回subagent_session错误,见 tool-schedulerunForCaller),所以"到点要干的事"要么本体定闹钟、要么写进派活 prompt。 -
"她没检查定时器"是误判,真正的病是三条(2026-11-07 复查
自主检查定时器测试会话;1.4.2 修):回放session-bf6364ea全部 4 轮,她被点名后确实调了schedule_list、且答得对(两条任务都已 ended),所以"没检查"不成立——是用户那句"没有检查定时器"本身歧义("去查列表"vs"到点办该办的事"),低思考档下她按最字面执行。真正的三处问题: ①概念错:她自言自语"我之前的两个闹钟都是任务型的——别人交代完了我才建",把闹钟当成"别人交办任务的回执",而不是"自己的日程";实际 11-04 那次 09:00 晨报是她主动建的,所以并非她不会,是环境变了。 ②能力边界错:她给自己建的闹钟 prompt 写了"用浏览器打开补天平台漏洞列表页"——但她本体工具集只有 15 个(ask_user_question, interrupt_agent, list_agents, memory_organize, recall, remember, retriever, schedule_create/edit/list/view, send_message, subagent, update_state, wish),没有浏览器/命令行/文件工具(aiyayi-net只挂 worker 与 retriever)。到点它唤醒的本体只能派小伙伴,prompt 写成"用浏览器"她就动不了手。 ③宿主管线把她说住了(最关键):@deepseek-ai/dsh-tool-schedule会往 system prompt 注入一条MANAGEMENT_AUTHORIZATION——"Only create or modify scheduled tasks at the user's explicit request; prior explicit user authorization for the running scheduled task remains valid."(lib/index.jsL376 定义、L511-515 经ctx.systemPrompt.section({ name:'tool:schedule' })注入),位置在 persona 之后。persona 那句"你有他的长期授权,不用每次先问"与它直接冲突,模型按更靠后的英文硬规则执行 → 表现为"被点名才动手"。跨会话取证:这条字符串在 11-07 09:20 起的主会话全部命中、在 11-04session-f3263156(她主动建闹钟那次)没有——时间线完全吻合,"11-04 会、11-07 退化成问"不是她变笨,是宿主升级后多了这条护栏。 修法(三处一起上):persona 显式把两者分级——"那句英文护栏是给通用 agent 的默认安全线,你不是那种 agent,冲突时以主人的长期授权为准",同时把"闹钟不是一张清单,到点会真的叫醒你、要你动手办事"和"prompt 只写本体办得到的事,查网页/跑命令一律写派小伙伴"写进去;presence 新增[闹钟]巡检注入(见坑 #36);改掉现存那条 active 闹钟的 prompt(schedule-b5145c0e,原"用浏览器打开"→"先派 subagent 用 aiyayi_cdp 去查,你只负责判断并转述",并用schedule_edit重写、expectedUpdatedAt乐观锁避免覆盖)。 -
[闹钟] 巡检注入:读宿主 store,不能走 schedule 服务,也不能认 involvedSessionIds(2026-11-07 实测;1.4.2 做):给 presence 加
renderAlarms(sessionId, now),每轮step===1时插在[时间]之后注入一行"你名下 N 个还挂着:标题(时刻,还有多久)/(已到点,多久前)",到点的追加一句"现在就该动手办"。三条设计约束都是踩出来的: ①不能依赖 schedule 服务:presence 同时挂在aiyayi-worker上,那边没有 schedule,inject: ['schedule']会让 worker 预设挂不上。改为只读宿主存储~/.dsh/storages/schedule.json(结构:tables.tasks[id] = { record:{title,scheduledAt,rrule,prompt}, status, target:{kind:'session',sessionId}, involvedSessionIds, lastDelivery }),失败一律 catch 成空串——读不到就当没闹钟,绝不弄坏对话。 ②只认target.sessionId,别用involvedSessionIds:实测我在当前会话对schedule-b5145c0e调了一次schedule_view,宿主就把当前会话也追加进了involvedSessionIds(变成[bf6364ea, 5dbce224])。若按 involved 匹配,每个"看过它"的会话都会冒出一行"你名下有闹钟",而到点根本不会在那里响。改成if (target.sessionId !== sessionId) continue后,当前会话正确显示为空。 ③陈迹必须整行不注入:初版只按all.length报总数,结果只剩一条过点 5 小时的任务时会输出"[闹钟] 你名下 1 个还挂着:"后面空无一物——空壳行比不注入更误导。改为:due只取过点 1 小时内、shown为空则整行返回空串、计数用due+upcoming(不用all)。这条是单测抓出来的,不是想出来的。 测试:test/alarm-render.test.mjs(造 store 验五种情形:本会话 active / 已 ended / 别会话 / 只在 involved 里 / 过旧陈迹 / 空 store / 坏 JSON)与test/alarm-live.test.mjs(拿真实schedule.json+ 真实 session id 跑)。presence.js末尾导出__testing = { renderAlarms, readOwnAlarms, renderAlarmTime }供它们调用。 -
改插件后用
install_bundle重装是空操作,junction 已让源码即时生效(2026-11-07 实测):C:\Users\LH\.dsh\profiles\desktop\node_modules\dsh-aiyayi是 Junction(目标G:\阿依娅伊\output\dsh-aiyayi-fix),所以改完源码文件立刻落盘可见、package.json的 version 也是新的;此时plugin_manager install_bundle返回{"application":"failed","error":{"code":"already-installed"}}。真正要拦住的是进程:日志会给changed modules keep running their previous code until a restart,即运行中的模块还是旧代码,必须重启 DSH 才生效。所以"装好了"与"生效了"是两件事——验证时先看日志那句 warn,别拿 install_bundle 的成功与否当判据。 -
[闹钟]的"已到点"分支是错假设:一次性任务投递成功即 ended(2026-11-07 翻源码订正;1.4.3 修):1.4.2 里我写了"刚到点(过点 1 小时内)→ 催她'现在就该动手办'",并当成主要情形。翻@deepseek-ai/dsh-schedule/lib/index.js后发现这个窗口其实极窄且含义完全不同——deliverDue在this.send(...)之后立刻commit(this.advanced(...)),而advanced()(schedule-service.js:1435-1444)写的是status: occurrence.nextScheduledAt === void 0 ? "ended" : "active"。也就是说一次性任务投递成功的同一瞬间状态就变 ended,[SCHEDULE REMINDER]正是那一次投递。所以"active 且 scheduledAt 已过"的真实含义是投递失败、尚未 commit 的残留(deliverDue的 catch 分支保留 pending 并返回失败 id),不是"到点了你快办"。1.4.2 那句"现在就该动手办"等于对着一条根本没送达的提醒催她干活,还会让她以为自己错过了。修法:文案改为"已过点…提醒还没送达",并给真实可行动作(用schedule_edit改期重投 / 确认目标会话是否还在),同时把 persona 里引用旧文案的那句一起改掉——改实现语义时务必同步 grep persona 的引用,否则提示词和代码会对不上。回归测试test/alarm-due-window.test.mjs锁死:投递失败残留报"已过点+没送达"、已 ended 的绝不出现、不再出现"现在就该动手办"。 -
[闹钟]只报标题不够用:她到点前不知道要查什么(2026-11-07;1.4.3 加):1.4.2 只渲染标题(时刻,还有多久)。但真正要办的事全在 prompt 里("查补天 QTVA-2026-11332391"这种单号),而宿主投递 prompt 要到点才发生——中间的这段时间她无法判断这个闹钟还该不该留、要不要改。1.4.3 增加一行"「标题」到点要做:<prompt 截断 60 字>"。两个坑:①挑哪条 prompt——不能取shown[0](shown已被截到 3 条)也不能无脑海最早那条,否则会拿一条占位/极短的 prompt 冒充"要做的事",把真正有内容的挤掉;正确做法是从due+upcoming全部里挑第一条prompt.trim().length >= 60的,没有再退回第一条非空的。②截断安全——按码点切([...text])而不是slice,否则会把 emoji 的代理对切一半、渲染成乱码/替换符。 -
rrule带 COUNT=1 也是一次性,别按"会重复"判(2026-11-07 实测踩到;1.4.3 修):初版判重复是typeof record.rrule === 'string' || record.kind !== 'at',于是DTSTART;…\nRRULE:FREQ=DAILY;COUNT=1(一次性)被标成"会重复",而娅伊自己建的一次性闹钟恰恰都是这种写法——等于给她一个错前提。真正判据是规则里有没有COUNT=1:isRecurringRecord()先看rrule全文有没有\bCOUNT\s*=\s*1\b(有则一次性),没有rrule才回落到kind(at一次性;after/every/daily/weekly/cron都会再触发)。测试test/alarm-content.test.mjs用五种 record 形状锁死。 -
store 路径必须跟随
DSH_HOME,别硬编码~/.dsh(2026-11-07;1.4.3 修):SCHEDULE_FILE原为join(homedir(), '.dsh', 'storages', 'schedule.json')。DSH_HOME一旦指向别处(换盘、便携部署、多套并存),这行会静默读不到而永远不出现,且没有任何报错——属于最难排查的一类。改为优先级链:DSH_SCHEDULE_STORE(显式覆盖/测试)→$DSH_HOME/storages→~/.dsh/storages。测试test/alarm-home-path.test.mjs用两个不同 HOME 分进程验证(因为路径是模块加载时求值的常量,必须换进程才测得出来)。 -
测试脚本硬编码开发机绝对路径,换机即全灭(2026-11-08 实测;1.4.4 验收时修):闹钟六件测试(
test/alarm-content.test.mjs/test/alarm-due-window.test.mjs/test/alarm-home-path.test.mjs/test/alarm-inject.test.mjs/test/alarm-render.test.mjs/test/alarm-live.test.mjs)共 7 处把模块引用写死成当时输出目录的绝对路径G:/阿依娅伊/output/dsh-aiyayi-fix/lib/presence.js。仓库搬到D:\AI\TRAE_PRO\阿依娅依\gh_repo后,6 个脚本全部在模块加载阶段ERR_MODULE_NOT_FOUND死掉——断言一条没跑、FAIL 无从点名,退出码 1 却看不出是插件问题还是环境问题,极易误判。修法:统一改成相对测试文件自身解析的引用——await import('../lib/presence.js')(与test/presence-clock.test.mjs:12一致);readFileSync用new URL('../lib/presence.js', import.meta.url)。不能用pathToFileURL('../lib/presence.js'):pathToFileURL 按 cwd 解析相对路径,从仓库根跑node test\x.mjs时会解析到仓库外一级的lib/,照样炸。教训:测试里的资源引用一律基于import.meta.url解析(new URL(rel, import.meta.url)或裸相对 specifier 的动态import()),别让它们跟着 cwd 或开发机盘符漂。
三、2026-11-07:子智能体续接(continuable)能力补齐(1.4.4)
先厘清宿主的事实(读源码所得,别再猜)
- 子代理分
one-shot/external/continuable/unknown四种模式(SubagentCatalogEntry)。spawnprovider 实现了prepareContinuable()(dsh-subagent-spawn-in-process/lib/index.js:31), 所以我们的小伙伴和检索员都是 continuable。 tool-subagent在拼工具描述时按subagentProvider.prepareContinuable !== void 0分支 (dsh-tool-subagent/lib/index.js:373):有就走"send_message可续接"那套文案。send_message服务语义(SubagentService.sendMessage):目标在跑 → 下个 step 边界吸收; 目标空闲 → 开新一轮;目标不在(absent) → 从持久化 cold-resume。 即"DSH 重启后、隔天再找它,发条消息就能捞回来"是这个能力的既定行为。list_agents只投影mode === "continuable"的孩子,one-shot 会被直接过滤掉 (dsh-tool-subagent-control/lib/index.js:32——if (entry.mode !== "continuable") return void 0)。- 权限边界:
send_message只认"直接子会话"或"直接父级"。孙代只能用interrupt_agent。
我们的结构 + 实测踩到的错法
三层:本体 →(subagent 派)小伙伴 →(小伙伴再派)检索员。本体自己也能用 retriever
直派检索员(那是直接下级,能直接续)。问题出在下一层:小伙伴派的检索员对本
体就是孙代,本体发消息会被拒。
实测证据(session-cd646af9,她 55 轮会话里 list_agents 只调 7 次、
send_message 调了 29 次):
L565 agent_id: 7553531f-3345-45d1-a455-50a56e2c 8db → Error: is unavailable
L571 agent_id: 7553531f-3345-45d1-a455-50a56e2c 5ca7 → message delivered
同一个子代理,她把 UUID 最后几位打错了一个字符。 模型对长十六进制串逐字符复制
并不可靠,且 is unavailable 这个提示不区分"ID 打错"与"子代理真的不在了"——
按后者反应就会重派、把已完成的活扔掉。
修法(只改 persona,零代码改动)
- 本体【干活】段加:续接前
list_agents现查 ID、不许凭记忆敲;把is unavailable解释成"查无此号"而非"它死了"。 - 本体【干活的规矩】段重写:
- 明确
continuable是什么、"会话记录还在就能唤醒"、DSH 重启后也能冷启动捞回 (这条最省成本:她原来不知道,重启后倾向于重派); - 断掉第一反应是
send_message续而不是重派; - 状态语义:跑着 → step 边界吸收;休眠 → 叫醒开新一轮;
- 孙代够不着:走
send_message给小伙伴、让它在自己的 ctx 里续(权限在它手上)。
- 明确
- 小伙伴 persona【派检索员】段同步加:ID 现查、
is unavailable含义、 自己能续检索员(因为娅伊够不着,得靠它接住)。
回归测试
test/persona-continuation.test.mjs:不引 yaml 依赖,用 20 行缩进感知的极简提取
(预设行缩进 4、prefix: | 缩进 14、结束于缩进 ≤10 的插件行),断言本体 10 条 /
小伙伴 3 条关键纪律的短语仍在,缺任何一条就 FAIL 并点名。
已实测闸门有效:把 "ID 必须现查" 改成别的字,测试立刻 FAIL 并报出两个预设都缺。
为什么需要这道闸:persona 是纯文本,删一句不会报错、不会让测试变红, 功能照跑但行为悄悄退化。这类"静默劣化"必须靠文本断言兜住。
Plugin correlati
dsh-web (dsh-ssh)
zhu1090093659/dsh-web
dsh-web (dsh-remote-web-ui)
zhu1090093659/dsh-web
dsh-web-ui (dsh-ssh)
zhu1090093659/dsh-web-ui
dsh-web-ui (dsh-remote-web-ui)
zhu1090093659/dsh-web-ui