Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-3E63DD?style=flat-square" alt="license"></a>
<a href="https://github.com/slow-stack/mneme/actions"><img src="https://img.shields.io/github/actions/workflow/status/slow-stack/mneme/ci.yml?style=flat-square&label=CI" alt="CI"></a>
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-22%2B-3E63DD?style=flat-square&logo=nodedotjs&logoColor=white" alt="node"></a>
<a href="https://github.com/slow-stack/mneme"><img src="https://img.shields.io/badge/tests-1479%20passed-3E63DD?style=flat-square" alt="tests"></a>
<a href="https://github.com/slow-stack/mneme"><img src="https://img.shields.io/badge/tests-1481%20passed-3E63DD?style=flat-square" alt="tests"></a>
<a href="https://codecov.io/gh/slow-stack/mneme"><img src="https://img.shields.io/codecov/c/github/slow-stack/mneme/main?style=flat-square" alt="coverage"></a>
<a href="https://github.com/awesome-dsh-plugin/awesome-dsh-plugin"><img src="https://awesome-dsh-plugin.com/badge.svg" alt="Awesome"></a>
</p>
Expand Down Expand Up @@ -183,7 +183,7 @@ dsh web

```bash
cd dsh-mneme && npm install
npm test # 1479 个测试
npm test # 1481 个测试
npm run stress # 三轴线压测
npm run sync # src → lib 同步
```
Expand Down Expand Up @@ -366,7 +366,7 @@ The plugin ships a zero-dependency stdio MCP server (standalone npm package **`m

```bash
cd dsh-mneme && npm install
npm test # 1479 tests
npm test # 1481 tests
npm run stress # three-axis stress test
npm run sync # src → lib sync
```
Expand Down
6 changes: 3 additions & 3 deletions dsh-mneme/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
[![npm version](https://img.shields.io/npm/v/@modusensus/dsh-mneme?color=blue&label=npm)](https://www.npmjs.com/package/@modusensus/dsh-mneme)
[![license](https://img.shields.io/badge/license-MIT-green)](LICENSE)
[![Awesome](https://awesome-dsh-plugin.com/badge.svg)](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
[![tests](https://img.shields.io/badge/tests-1479%20passed-success)](https://github.com/slow-stack/mneme)
[![tests](https://img.shields.io/badge/tests-1481%20passed-success)](https://github.com/slow-stack/mneme)
[![CI](https://img.shields.io/github/actions/workflow/status/slow-stack/mneme/ci.yml)](https://github.com/slow-stack/mneme/actions)
[![node](https://img.shields.io/badge/node-22%2B-blue)](https://nodejs.org)
[![npm downloads](https://img.shields.io/npm/d18m/@modusensus/dsh-mneme.svg?color=blue&label=downloads)](https://www.npmjs.com/package/@modusensus/dsh-mneme)
Expand Down Expand Up @@ -584,7 +584,7 @@ src/
├── api.js # HTTP 路由(Web 面板数据通道,含 /conflicts 冲突队列)
└── index.js # 插件接线
lib/ # src 的同步分发产物(npm run sync;发布前由 root prepack 的 check-sync.js 校验一致性;唯一手写例外 lib/client.js——Web 面板 bundle,sync 不覆盖)
test/ # 1479 个 node:test 测试(审计与三轴线压测不变量;src↔lib 一致性由 scripts/check-sync.js 发布闸门校验)
test/ # 1481 个 node:test 测试(审计与三轴线压测不变量;src↔lib 一致性由 scripts/check-sync.js 发布闸门校验)
scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压测 · sync-lib.js 同步 · check-sync.js 发布闸门 · benchmark-recall.js / benchmark-embed.js / benchmark-rerank.js 基准 · sync-test-badge.mjs 测试徽章 · build-runtime-manifest.mjs 运行时清单
```

Expand All @@ -593,7 +593,7 @@ scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压
```bash
cd dsh-mneme
npm install # 安装 peer 依赖(以 devDependencies 形式,用于本地测试)
npm test # 运行 1479 个测试
npm test # 运行 1481 个测试
npm run stress # 三轴线压测:长会话检索 / 冲突仲裁 / 多 Agent 并发(离线 mock LLM)
npm run sync # 把 src/ 同步到 lib/(发布时由 prepack 钩子自动执行)
```
Expand Down
11 changes: 8 additions & 3 deletions dsh-mneme/lib/summarize.js
Original file line number Diff line number Diff line change
Expand Up @@ -286,9 +286,14 @@ function collectMessages(session, maxChars = 8000, language = "zh", afterSeq, th
break;
}
case "tool/result": {
const result = Array.isArray(data?.message?.content)
? data.message.content.find((block) => block?.type === "tool-result")
: undefined;
// 工具结果有两种形状:旧形状把载荷与 isError 装在 content 内的
// `type:"tool-result"` 块上;DSH 0.2.x 起内核改为直接投递 `role:"tool"`
// 消息本身,content 里只有 `type:"text"` 块、isError 挂在消息上。只认
// 旧块形状时新形状落进 undefined 分支,工具输出与工具报错被静默丢弃。
const message = data?.message;
const blocks = Array.isArray(message?.content) ? message.content : [];
const legacy = blocks.find((block) => block?.type === "tool-result");
const result = legacy ?? (message?.role === "tool" ? message : undefined);
const out = textOf(result?.content);
const status = result?.isError === true ? STR.statusFail[language] : STR.statusOk[language];
lines.push(STR.transcriptToolResult[language](status, trim(out, 500)));
Expand Down
11 changes: 8 additions & 3 deletions dsh-mneme/src/summarize.js
Original file line number Diff line number Diff line change
Expand Up @@ -286,9 +286,14 @@ function collectMessages(session, maxChars = 8000, language = "zh", afterSeq, th
break;
}
case "tool/result": {
const result = Array.isArray(data?.message?.content)
? data.message.content.find((block) => block?.type === "tool-result")
: undefined;
// 工具结果有两种形状:旧形状把载荷与 isError 装在 content 内的
// `type:"tool-result"` 块上;DSH 0.2.x 起内核改为直接投递 `role:"tool"`
// 消息本身,content 里只有 `type:"text"` 块、isError 挂在消息上。只认
// 旧块形状时新形状落进 undefined 分支,工具输出与工具报错被静默丢弃。
const message = data?.message;
const blocks = Array.isArray(message?.content) ? message.content : [];
const legacy = blocks.find((block) => block?.type === "tool-result");
const result = legacy ?? (message?.role === "tool" ? message : undefined);
const out = textOf(result?.content);
const status = result?.isError === true ? STR.statusFail[language] : STR.statusOk[language];
lines.push(STR.transcriptToolResult[language](status, trim(out, 500)));
Expand Down
60 changes: 60 additions & 0 deletions dsh-mneme/test/summarize.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,25 @@ function toolResultEvent(text, isError, seq) {
};
}

// DSH 0.2.x 内核对工具结果的实际投递形状:内核给的是 `role:"tool"` 消息本身,
// content 里只有 `type:"text"` 块、isError 挂在消息上,不再有 tool-result 块。
// 蒸馏曾只认旧块形状,于是真实会话里的工具输出与工具报错全部被静默丢弃。
function toolRoleResultEvent(text, isError, seq) {
return {
seq,
type: "tool/result",
data: {
message: {
role: "tool",
source: { kind: "tool", callId: `call-${seq}` },
toolCallId: `call-${seq}`,
content: [{ type: "text", text }],
isError
}
}
};
}

function codeDispatchEvent(text, isError, seq) {
return {
seq,
Expand Down Expand Up @@ -1058,6 +1077,47 @@ test("collects real tool result and code dispatch payloads with failure status",
assert.match(transcript, /代码执行(成功):代码执行结果/);
});

test("collects tool results delivered as role:tool messages, keeping the legacy block shape", async () => {
const { events, calls } = setup({ distillRateLimitIntervalMs: 0 });
const handler = events.find((e) => e.name === "session/event").fn;
const session = {
id: "s-tool-role-shape",
requestHeader: () => ({ config: { provider: "deepseek", model: "deepseek-chat" } }),
events: [
userMessage("检查工具结果两种形状", 1),
toolResultEvent("旧形状载荷", false, 2),
toolRoleResultEvent("新形状载荷", false, 3),
toolRoleResultEvent("TypeError: tool crashed", true, 4),
{ seq: 5, type: "turn/end" }
]
};

await handler(session, { seq: 5, type: "turn/end" });
const transcript = calls[0].messages.find((message) => message.role === "user").content[0].text;
assert.match(transcript, /工具结果(成功):旧形状载荷/);
assert.match(transcript, /工具结果(成功):新形状载荷/);
assert.match(transcript, /工具结果(失败):TypeError: tool crashed/);
});

test("keeps prefix trimming for tool results delivered as role:tool messages", async () => {
const { events, calls } = setup({ distillRateLimitIntervalMs: 0 });
const handler = events.find((e) => e.name === "session/event").fn;
const session = {
id: "s-tool-role-trim",
requestHeader: () => ({ config: { provider: "deepseek", model: "deepseek-chat" } }),
events: [
userMessage("检查新形状截断", 1),
toolRoleResultEvent(`OUT_START ${"c".repeat(550)} OUT_END`, false, 2),
{ seq: 3, type: "turn/end" }
]
};

await handler(session, { seq: 3, type: "turn/end" });
const transcript = JSON.stringify(calls[0].messages);
assert.ok(transcript.includes("OUT_START"));
assert.ok(!transcript.includes("OUT_END"), "role:tool 结果同样走 500 字符前缀截断");
});

test("collects subagent delivery once across inbox and user-message views", async () => {
const { events, calls } = setup({ distillRateLimitIntervalMs: 0 });
const handler = events.find((e) => e.name === "session/event").fn;
Expand Down
Loading