anweat/dsh-assistant-message-forge
DSH assistant message forge: create/modify/inject test assistant messages, import session.jsonl(.zstd) session logs (client plugin)
Listed
0
Session
Bundle verified
Preview
What it does
Create/modify/inject test assistant messages and import/repair session.jsonl(.zstd) session logs into new sessions.
Best for
- DSH developers testing how crafted assistant messages, reasoning, provider metadata, tool results, or usage fields affect a session.
- Session debugging workflows that need card-based inspection and controlled surface replacement of recorded context.
- Users who need to inspect damaged session.jsonl or zstd logs and create a repaired session without modifying the original log.
Not ideal for
- Ordinary chat workflows that do not need synthetic messages, context mutation, or session-log repair.
- Sessions with an unclosed active turn, because message injection and applying edits are refused while the session is running.
- Imports that must preserve non-text blocks such as images or tool calls during injection; these are counted as unsupported rather than carried over.
README
dsh-assistant-message-forge(消息锻造台)
双面(Host + Client)DSH 插件:在会话区新增一个 消息锻造台 页签,核心是 详细上下文解析 → 记录 → 卡片化 → 动态编辑 → 开放修改:
-
context/refresh把当前会话完整解析成一张张ContextCard(turn/step 边界、user/assistant 消息、tool call/result、request header、 其他 log-only 事件;assistant/chunk按轮折叠计数); - 解析结果连同卡片 overrides 一起 记录 到
$DSH_HOME/assistant-message-forge/context-<sessionId>.json; - 页签内按轮分组 卡片化 展示,含 surface 折叠状态(原始被替换 / 替换生效中);
- 卡片可 动态编辑:文本/reasoning/provider/model/usage、工具名与 arguments、工具结果文本;编辑先保存为记录,也可 保存并应用到会话 (surface replace 直接改写当前会话中的原消息)。
此外保留 v1 能力:创建/修改/删除/注入 assistant 草稿、上传识别
session.jsonl(.zstd);导入区还会做完整性预览,并可把安全可解释的损坏
恢复为新的 session-repaired-* 会话。
能力
| 能力 | 入口 | 说明 |
|---|---|---|
| 上下文解析+记录 | 会话上下文 → 刷新解析 | 全事件分类解析并落盘为 context 记录 |
| 上下文卡片化 | 会话上下文卡片流 | 按轮分组,边界/消息/工具/请求/其他卡片 + surface 状态 |
| 卡片动态编辑 | 卡片 → 编辑 → 保存记录 | 修改只写入记录 override,刷新解析不丢 |
| 应用到会话 | 卡片 → 保存并应用到会话 / 应用到会话 | user/assistant/tool-result 用 surface replace 替换原消息,模型上下文即时生效 |
| 添加/修改/删除草稿 | 草稿区按钮 | 草稿持久化在 drafts.json
|
| 注入到会话 | 草稿行 → 注入到当前会话 | 完整合成轮 + flush |
| 导入识别 sessionlog | 底部文件选择 → 导入并识别 | zstd/JSONL 解码 + packed chunk 展开 + 事件识别 |
| 修复损坏 sessionlog | 完整性预览 → 创建修复会话 | 后写分支覆盖 + 官方 crash closer + 官方 seed 校验;原日志只读 |
| 复用识别结果 | 识别行 → 存为草稿 / 注入 | 保留 reasoning+text/provider/model/usage |
架构
浏览器客户端 DSH host (Node)
ForgeView(conversation.view 页签)
├─ ContextPanel:解析记录卡片流 + 动态编辑
├─ 草稿区:添加/修改/删除/注入按钮
├─ sessionlog 文件选择 + 识别/修复预览
└── connection.rpc.call(AMF_RPC_CHANNEL) ──▶ /dsh-assistant-message-forge
├─ DraftStore(drafts.json)
├─ ContextRecordStore(context-*.json + overrides)
├─ parseContext / surface replace apply
├─ parse/repairSessionLogBytes(node:zlib zstd)
└─ injectMessage(session.append + flush)
- Host 半通过
ctx.connection.rpc.handle()注册通用 Connection RPC 通道(loopback 信任边界),不修改 apiproxy 允许列表。 - Client 半注册
conversation.view页签(scope: session),组件经sessionId标准 prop 拿到当前会话。
RPC 端点
通道:/dsh-assistant-message-forge
| endpoint | payload | 返回 |
|---|---|---|
drafts/list |
{} |
AssistantDraft[] |
drafts/save |
{ draft: { id?, title?, content, reasoning?, provider?, model? } } |
保存后的 AssistantDraft
|
drafts/delete |
{ id } |
boolean |
session/inject |
{ sessionId, message: { content, reasoning?, provider?, model?, usage? } } |
{ sessionId, turn, step, seq, messageId, flushed } |
context/load |
{ sessionId } |
已记录的 ContextSnapshot 或 null
|
context/refresh |
{ sessionId } |
重新解析并记录后的 ContextSnapshot
|
records/update |
{ sessionId, key, patch } |
合并卡片 override 后的 ContextSnapshot
|
records/reset |
{ sessionId } |
丢弃全部 override 后的 ContextSnapshot 或 null
|
context/apply |
{ sessionId, key } |
{ sessionId, replacedSeq, newSeq, type, turn?, step?, flushed } |
sessionlog/parse |
{ name, dataBase64 } |
SessionLogParseResponse |
sessionlog/repair-preview |
{ name, dataBase64 } |
SessionLogRepairReport |
sessionlog/repair-create |
{ name, dataBase64 } |
{ sessionId, flushed, report } |
错误统一走 RpcResult 的 { ok:false, error:{ code:'internal', ... } } 分支;参数校验失败、会话不存在、当前会话存在未闭合 turn 时都会返回可读 message。
卡片与修改语义
-
卡片解析覆盖:
turn/start|end、step/start|end、user/message、assistant/message(reasoning/text/provider/model/usage)、tool/call、tool/result、request/header、request/context、其余 log-only 事件。assistant/chunk不逐条成卡,按轮统计chunkCount。 -
记录优先:
records/update只改 override;之后context/refresh重新解析时 会合并 override,不会丢。records/reset丢弃全部记录修改。 -
应用到会话 = surface replace:对
user/message、assistant/message、tool/result追加一个surfaceOp: {op:'replace', start:seq, end:seq}事件, 原节点被 shadow(模型deriveMessages用新内容),原始 append 事件仍保留在 日志与官方对话流中;卡片流会同时显示「原始已被替换」和「替换生效」两张卡。 -
tool/result 特殊限制:DSH surface 校验只允许改写 tool-result 的
content;message id/source/isError/error wrapper 必须保持原值,因此isError在记录里可改,但应用时按原值写回。 - 会话正在运行(存在未闭合 turn)时注入和应用都会拒绝,避免破坏执行边界。
-
导入识别的投影:识别结果把 content 块投影为
text+reasoning两条;tool-call、image等非文本块计数为unsupportedBlocks,注入时不会携带。 -
传输上限:上传 base64 ≤ 128 MiB;解压后 ≤ 128 MiB;识别列表每批最多 200 条(
truncated标记)。
会话修复语义与边界
-
后写分支覆盖:物理日志出现
seq < 当前逻辑长度时,从该 seq 回滚候选尾部, 再接入后写分支;报告会列出fromSeq和丢弃事件数。 - 不猜缺口:坏 JSON、坏 packed row 或向前跳号会让扫描保守停止;不会伪造缺失业务事件。
-
官方闭合:候选前缀交给
@deepseek-ai/dsh-session的interruptedTurnClosers()补齐未完成 tool/step/turn。 -
官方终审:使用
Session.create(seed)重新验证从 0 连续的 seq、事件 envelope、 surface append/replace 引用和 JSON 可持久化约束;失败则禁用创建按钮。 -
不可覆盖:插件不截断、不重命名、不写回源
session.jsonl(.zstd);只创建新的session-repaired-*会话,并把原 session id 记录为parentSession。
目录结构
assistant-message-forge/
├── cordis.patch.yml # bundle patch:insert 一行,包名即 loader 行名
├── package.json # dsh.client + dsh.bundle + exports["./client"]
├── tsconfig.json # tsc:lib/index.js + lib/types(Host 半 + 中间 Client JS)
├── tsdown.config.ts # tsdown:lib/client.js 浏览器闭包工厂
├── scripts/
│ ├── check-client-bundle.mjs # 校验 client bundle 只 require 模块表内依赖
│ ├── test-session-repair.mjs # 分支回退 + crash closer 纯函数回归
│ └── e2e-smoke.mjs # 编辑/apply/导入/修复新会话全流程冒烟
└── src/
├── index.ts # Host 半:RPC 通道 + 全部端点路由
├── store.ts # 草稿 JSON 持久化
├── context.ts # 详细上下文解析 + context 记录 + surface replace apply
├── sessionlog.ts # zstd 帧扫描 + JSONL 解码 + 事件识别
├── shared-types.ts # 双面共享 wire 类型
└── client/
├── index.ts # Client 半:注册 conversation.view 页签
├── ForgeView.tsx # 锻造台三段式 UI(上下文/草稿/导入)
├── ContextPanel.tsx # 解析记录卡片流 + 动态编辑
├── ForgeView.module.css
└── locales.ts # zh/en + LocaleNamespaceMap 增广
构建
cd D:/codeproject/dsh-plugin/plugins/assistant-message-forge
pnpm install
pnpm run build # rm -rf lib && tsc && tsdown
pnpm run test:session-repair
pnpm run test:client-bundle
产物:
-
lib/index.js— Host 半(@deepseek-ai/*import 保持外部,由安装 profile 解析) -
lib/client.js— 浏览器闭包工厂(window.__ModuleLoader__.load({id:"dsh-assistant-message-forge"})) -
lib/types/**— 声明文件
激活(Web UI 需要 bundle 安装,不能用裸 --patch)
--patch 只能把 Host 源码挂进插件树;dsh.client 扫描器按包名经 require.resolve 发现 exports["./client"],因此浏览器半必须作为 bundle 安装进 profile:
cd D:/codeproject/deepseek-harness
# 独立测试 profile(第一次会只初始化 base,手动把 web-app bundle 加进 dsh.profile.bundles)
pnpm dsh plugin --profile amf-test add D:/codeproject/dsh-plugin/plugins/assistant-message-forge
pnpm dsh --profile amf-test --port 3090
# 正式使用:装进默认 web profile,重启 dsh web 生效
pnpm dsh plugin --profile web add D:/codeproject/dsh-plugin/plugins/assistant-message-forge
pnpm dsh web
dsh web是--profile web的硬别名;给自定义 profile 启动 Web 应用要用pnpm dsh --profile <name> --port <port>,并保证该 profile 的dsh.profile.bundles依次包含@deepseek-ai/dsh-base、@deepseek-ai/dsh-web-app、dsh-assistant-message-forge。
开发期热更
- Client 半:
client-hmr会 watch 所有已挂载 bundle 的lib/client.js。改完src/client/**后pnpm run build,浏览器自动拉取新 rev(本插件构建约 1 秒)。 - Host 半:web profile 的 loader HMR 关闭;改
src/index.ts/src/store.ts/src/sessionlog.ts后需重启dsh web。
E2E 冒烟
对运行中的实例执行按钮级验证(创建一次性会话 → 上下文刷新解析 → 助手卡片记录编辑/应用 → 丢弃记录修改 → 草稿添加/修改/注入 → 导入识别 → 损坏日志预览/创建修复会话 → 删除草稿):
$env:AMF_URL='http://127.0.0.1:3090'
$env:SESSION_LOG=$env:DSH_SESSION_JSONL
node scripts/e2e-smoke.mjs
脚本用 D:/codeproject/dsh-browser/node_modules/playwright(可用 PLAYWRIGHT_ROOT 覆盖),
先经 RPC 创建一个 workspace-attached 的一次性会话再逐项点击中文 UI,避免污染真实会话;
PLAYWRIGHT_CHANNEL=chrome 可显式使用本机 Chrome,AMF_HEADED=1 可打开可见浏览器窗口。
Frequently Asked QuestionsFAQ
Use the verified command dsh plugin --profile default add github:anweat/dsh-assistant-message-forge in a DSH-enabled shell. The command resolves the public package metadata and keeps the plugin attached to the catalog identity shown on this page.
Compatibility follows the bundle and profile status shown above. If a profile is not detected, keep the plugin disabled there and check the repository documentation before enabling it in production.
The GitHub link and activity metadata are the source of truth for releases and maintenance. Revisit this page after a new release to confirm the catalog has observed the latest version.