@modusensus/dsh-mneme 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +178 -179
- package/cordis.patch.yml +15 -0
- package/lib/api.js +59 -59
- package/lib/client.js +187 -187
- package/lib/config.js +15 -15
- package/lib/dream/decisions.js +121 -121
- package/lib/dream.js +205 -205
- package/lib/index.js +86 -86
- package/lib/inject.js +22 -22
- package/lib/mirror.js +131 -131
- package/lib/service.js +157 -157
- package/lib/store.js +230 -230
- package/lib/summarize.js +171 -171
- package/lib/tools.js +221 -221
- package/package.json +58 -32
- package/src/api.js +59 -59
- package/src/config.js +15 -15
- package/src/dream/decisions.js +121 -121
- package/src/dream.js +205 -205
- package/src/index.js +86 -86
- package/src/inject.js +22 -22
- package/src/mirror.js +131 -131
- package/src/service.js +157 -157
- package/src/store.js +230 -230
- package/src/summarize.js +171 -171
- package/src/tools.js +221 -221
package/README.md
CHANGED
|
@@ -1,179 +1,178 @@
|
|
|
1
|
-
# dsh-mneme
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
|
23
|
-
|
|
24
|
-
| `
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
###
|
|
32
|
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
###
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
|
110
|
-
|
|
111
|
-
| `
|
|
112
|
-
| `
|
|
113
|
-
| `
|
|
114
|
-
| `
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
│
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
│
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
├──
|
|
146
|
-
├──
|
|
147
|
-
├──
|
|
148
|
-
├──
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
├──
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
- [
|
|
174
|
-
- [
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
MIT
|
|
1
|
+
# dsh-mneme
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@modusensus/dsh-mneme)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
|
|
6
|
+
[](https://github.com/modusensus/dsh-mneme)
|
|
7
|
+
|
|
8
|
+
> 给 DeepSeek Harness 的跨会话记忆插件:让 Agent 记住你、记住项目、自动整理记忆。**Mneme**(Μνήμη)——希腊记忆女神 Mnemosyne 之名,掌管记忆与梦境,正如 autoDream 在后台巩固记忆。
|
|
9
|
+
|
|
10
|
+
`dsh-mneme` 是一个 [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) 插件,为 Agent 提供持久的跨会话记忆能力。它借鉴了 Claude 的 **Dream 机制** 与 cc-haha / Claude Code 的 **autoDream** 实现思路——不仅**存储**记忆,还会**自动巩固**(去重、合并、冲突裁决、摘要生成),让记忆库越用越精炼。
|
|
11
|
+
|
|
12
|
+
## ✨ 功能
|
|
13
|
+
|
|
14
|
+
### 记忆存储(SQLite + Markdown 镜像)
|
|
15
|
+
|
|
16
|
+
- **SQLite 主存储**:`~/.dsh/memory/memory.db`,`node:sqlite` 内置,零原生依赖
|
|
17
|
+
- **Markdown 镜像**:`preferences.md` / `projects.md` / `decisions.md` / `history.md` / `summary.md`,人类可读、可手工编辑(**人工修改优先**合并回库)
|
|
18
|
+
- **4+1 种记忆类型**:`preference`(偏好)/ `project`(项目)/ `decision`(决策)/ `history`(历史)/ `summary`(总览)
|
|
19
|
+
|
|
20
|
+
### 模型工具(6 个)
|
|
21
|
+
|
|
22
|
+
| 工具 | 功能 |
|
|
23
|
+
|------|------|
|
|
24
|
+
| `memory_save` | 记录一条记忆(自动按标题去重合并) |
|
|
25
|
+
| `memory_search` | 全文搜索(中文子串友好) |
|
|
26
|
+
| `memory_list` | 按类型分页列出 |
|
|
27
|
+
| `memory_update` | 修改已有记忆 |
|
|
28
|
+
| `memory_delete` | 删除记忆 |
|
|
29
|
+
| `memory_forget` | 抑制注入(降权不删除,可恢复) |
|
|
30
|
+
|
|
31
|
+
### 自动注入 + 会话摘要
|
|
32
|
+
|
|
33
|
+
- **自动注入**:新会话开局注入记忆摘要(`summary` 优先 + 少量高重要性条目)
|
|
34
|
+
- **会话摘要**:`turn/end` 时用 LLM 提炼本次会话的偏好/决策/教训,自动入库(过滤 plugin 注入上下文,避免污染)
|
|
35
|
+
|
|
36
|
+
### autoDream 自动记忆整理 🧠
|
|
37
|
+
|
|
38
|
+
- **触发**:记忆数 > 10 或总字符 > 5000 时,异步自动触发(不阻塞写入)
|
|
39
|
+
- **决策清单式整理**:LLM 输出 `keep` / `merge` / `archive` / `conflict` 决策清单,服务端校验后逐条应用
|
|
40
|
+
- `merge`:合并主题相近的条目,保留信息最完整者
|
|
41
|
+
- `archive`:归档过时/冗余条目(可恢复,不物理删除)
|
|
42
|
+
- `conflict`:裁决矛盾信息,胜者保留、败者归档并追加溯源注释
|
|
43
|
+
- **摘要生成**:整理后生成"记忆库总览"(单一实例),作为下次会话的优先注入
|
|
44
|
+
- **Fail-safe**:非法 LLM 输出(未知 id / 非法 action / 跨类型合并 / 越界 importance)拒绝整单,绝不破坏记忆库
|
|
45
|
+
|
|
46
|
+
### Web 记忆面板
|
|
47
|
+
|
|
48
|
+
侧边栏"记忆"按钮 → 模态面板:按类型浏览、全文搜索、查看详情。
|
|
49
|
+
|
|
50
|
+
## 📦 安装
|
|
51
|
+
|
|
52
|
+
### 前置条件
|
|
53
|
+
|
|
54
|
+
- [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(DSH)
|
|
55
|
+
- Node 24+(`node:sqlite`)
|
|
56
|
+
|
|
57
|
+
### 安装步骤
|
|
58
|
+
|
|
59
|
+
#### 方式一:npm 安装(推荐)
|
|
60
|
+
|
|
61
|
+
dsh-mneme 是一个 **bundle**(声明了 `dsh.bundle` manifest),安装即自动激活,无需手动写配置:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# 1. 安装插件(自动注册 bundle 层)
|
|
65
|
+
dsh plugin --profile web add @modusensus/dsh-mneme
|
|
66
|
+
|
|
67
|
+
# 2. 重启
|
|
68
|
+
dsh web
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
> 如需自定义配置(阈值、延迟等),可在 `~/.dsh/profiles/web/cordis.patch.yml` 中按 `id: dsh-mneme` 覆盖默认值(见下方配置表)。
|
|
72
|
+
|
|
73
|
+
#### 方式二:从源码安装
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
git clone https://github.com/modusensus/dsh-mneme.git
|
|
77
|
+
cd dsh-mneme
|
|
78
|
+
dsh plugin --profile web add .
|
|
79
|
+
dsh web
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
#### 自定义配置(可选)
|
|
83
|
+
|
|
84
|
+
默认配置即可用。如需调整,在 `~/.dsh/profiles/web/cordis.patch.yml` 中覆盖:
|
|
85
|
+
|
|
86
|
+
```yaml
|
|
87
|
+
- id: dsh-mneme
|
|
88
|
+
name: '@modusensus/dsh-mneme'
|
|
89
|
+
config:
|
|
90
|
+
memoryDir: ~/.dsh/memory
|
|
91
|
+
autoInject: true
|
|
92
|
+
autoSummarize: true
|
|
93
|
+
maxInjectedItems: 5
|
|
94
|
+
importanceThreshold: 3
|
|
95
|
+
autoDream: true
|
|
96
|
+
dreamThresholdCount: 10
|
|
97
|
+
dreamThresholdChars: 5000
|
|
98
|
+
dreamDelayMs: 2000
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## ⚙️ 配置
|
|
102
|
+
|
|
103
|
+
| 键 | 默认值 | 说明 |
|
|
104
|
+
|----|--------|------|
|
|
105
|
+
| `memoryDir` | `~/.dsh/memory` | 记忆存储目录(SQLite + Markdown) |
|
|
106
|
+
| `autoInject` | `true` | 会话启动自动注入记忆 |
|
|
107
|
+
| `autoSummarize` | `true` | 会话结束自动提炼摘要 |
|
|
108
|
+
| `maxInjectedItems` | `5` | 最多注入几条记忆 |
|
|
109
|
+
| `importanceThreshold` | `3` | 注入的最低重要性(1-5) |
|
|
110
|
+
| `autoDream` | `true` | 自动记忆整理开关 |
|
|
111
|
+
| `dreamThresholdCount` | `10` | 触发整理的记忆条数阈值 |
|
|
112
|
+
| `dreamThresholdChars` | `5000` | 触发整理的总字符阈值 |
|
|
113
|
+
| `dreamDelayMs` | `2000` | 整理异步延迟(去抖) |
|
|
114
|
+
| `dreamProvider` / `dreamModel` | 空 | dream 的 LLM 路由回退(默认用 agent 默认模型) |
|
|
115
|
+
|
|
116
|
+
## 🏗️ 架构
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
┌─────────────────────────────────────────────────┐
|
|
120
|
+
│ 存储层:SQLite (archived/forgotten 状态) │
|
|
121
|
+
│ + Markdown 镜像(人工可编辑,双向同步) │
|
|
122
|
+
├─────────────────────────────────────────────────┤
|
|
123
|
+
│ 服务层:saveWithDedupe / injectCandidates │
|
|
124
|
+
│ / mergeHumanEdits / onWrite 钩子 │
|
|
125
|
+
├─────────────────────────────────────────────────┤
|
|
126
|
+
│ 模型接口:6 个工具 + 自动注入 + 会话摘要 │
|
|
127
|
+
├─────────────────────────────────────────────────┤
|
|
128
|
+
│ autoDream:阈值调度 → LLM 决策清单 │
|
|
129
|
+
│ → 校验(fail-safe)→ 应用 → 摘要 │
|
|
130
|
+
├─────────────────────────────────────────────────┤
|
|
131
|
+
│ Web 面板:侧边栏入口 + 浏览/搜索/详情 │
|
|
132
|
+
└─────────────────────────────────────────────────┘
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**源码结构**:
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
src/
|
|
139
|
+
├── store.js # SQLite 存储(CRUD、搜索、归档/遗忘、schema 迁移)
|
|
140
|
+
├── mirror.js # Markdown 镜像(渲染/解析,人工优先)
|
|
141
|
+
├── service.js # 领域逻辑(去重合并、注入筛选、写入钩子)
|
|
142
|
+
├── config.js # schemastery 配置 schema
|
|
143
|
+
├── tools.js # 6 个模型工具(defineTool)
|
|
144
|
+
├── inject.js # systemPrompt.context 动态注入
|
|
145
|
+
├── summarize.js # 会话结束 LLM 摘要
|
|
146
|
+
├── dream.js # autoDream 调度 + runDream(LLM 决策 + 摘要)
|
|
147
|
+
├── dream/decisions.js# 决策校验(fail-safe)+ 决策应用
|
|
148
|
+
├── api.js # HTTP 路由(Web 面板数据通道)
|
|
149
|
+
└── index.js # 插件接线
|
|
150
|
+
lib/
|
|
151
|
+
├── client.js # Web 面板(手写 ModuleLoader bundle)
|
|
152
|
+
└── *.js # src 的同步分发产物
|
|
153
|
+
test/ # 106 个 node:test 测试
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## 🧪 开发
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
cd dsh-mneme
|
|
160
|
+
npm install # 安装 peer 依赖(以 devDependencies 形式,用于本地测试)
|
|
161
|
+
npm test # 运行 106 个测试(--test-isolation=none 用于受限沙箱,禁止子进程 spawn)
|
|
162
|
+
npm run sync # 把 src/ 同步到 lib/(发布时由 prepack 钩子自动执行)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
> 注:`npm test` 使用 `--test-isolation=none` 适配受限沙箱;普通环境可直接 `node --test`。
|
|
166
|
+
> `lib/` 是 `src/` 的同步分发产物(`npm run sync`),其中 `lib/client.js` 为手写 Web 面板源码,不受同步影响。
|
|
167
|
+
|
|
168
|
+
## 📄 设计文档
|
|
169
|
+
|
|
170
|
+
> 设计文档位于仓库根 `docs/`,链接以 `../docs/` 相对路径指向(GitHub 上从本目录打开可正常跳转)。
|
|
171
|
+
|
|
172
|
+
- [记忆库设计](../docs/superpowers/specs/2026-08-13-dsh-memory-design.md)
|
|
173
|
+
- [autoDream 设计](../docs/superpowers/specs/2026-08-13-dsh-memory-autodream-design.md)
|
|
174
|
+
- [实施计划](../docs/superpowers/plans/2026-08-13-dsh-memory-autodream.md)
|
|
175
|
+
|
|
176
|
+
## 📜 License
|
|
177
|
+
|
|
178
|
+
MIT
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# dsh-mneme bundle patch — the layer applied when a profile lists this bundle.
|
|
2
|
+
# Installed via: dsh plugin --profile <name> add @modusensus/dsh-mneme
|
|
3
|
+
- insert:
|
|
4
|
+
- id: dsh-mneme
|
|
5
|
+
name: '@modusensus/dsh-mneme'
|
|
6
|
+
config:
|
|
7
|
+
memoryDir: ~/.dsh/memory
|
|
8
|
+
autoInject: true
|
|
9
|
+
autoSummarize: true
|
|
10
|
+
maxInjectedItems: 5
|
|
11
|
+
importanceThreshold: 3
|
|
12
|
+
autoDream: true
|
|
13
|
+
dreamThresholdCount: 10
|
|
14
|
+
dreamThresholdChars: 5000
|
|
15
|
+
dreamDelayMs: 2000
|
package/lib/api.js
CHANGED
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
import { URL } from "node:url";
|
|
2
|
-
|
|
3
|
-
function sendJson(res, status, payload) {
|
|
4
|
-
res.writeHead(status, { "Content-Type": "application/json; charset=utf-8" });
|
|
5
|
-
res.end(JSON.stringify(payload));
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function createApi(ctx, service) {
|
|
9
|
-
const disposers = [];
|
|
10
|
-
|
|
11
|
-
// /api/dsh-mneme prefix fallback → 404 JSON for unknown sub-paths
|
|
12
|
-
disposers.push(ctx.webServer.register({
|
|
13
|
-
kind: "prefix",
|
|
14
|
-
path: "/api/dsh-mneme",
|
|
15
|
-
handler(req, res) {
|
|
16
|
-
sendJson(res, 404, { error: "not-found" });
|
|
17
|
-
}
|
|
18
|
-
}));
|
|
19
|
-
|
|
20
|
-
disposers.push(ctx.webServer.register({
|
|
21
|
-
kind: "exact",
|
|
22
|
-
path: "/api/dsh-mneme/list",
|
|
23
|
-
handler(req, res) {
|
|
24
|
-
try {
|
|
25
|
-
const url = new URL(req.url, "http://localhost");
|
|
26
|
-
const type = url.searchParams.get("type") ?? undefined;
|
|
27
|
-
const limit = Number(url.searchParams.get("limit") ?? 50);
|
|
28
|
-
const offset = Number(url.searchParams.get("offset") ?? 0);
|
|
29
|
-
const items = service.toApiList(service.list({ type, limit, offset }));
|
|
30
|
-
sendJson(res, 200, { items, total: service.count(type) });
|
|
31
|
-
} catch {
|
|
32
|
-
sendJson(res, 500, { error: "internal" });
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}));
|
|
36
|
-
|
|
37
|
-
disposers.push(ctx.webServer.register({
|
|
38
|
-
kind: "exact",
|
|
39
|
-
path: "/api/dsh-mneme/search",
|
|
40
|
-
handler(req, res) {
|
|
41
|
-
try {
|
|
42
|
-
const url = new URL(req.url, "http://localhost");
|
|
43
|
-
const q = url.searchParams.get("q") ?? "";
|
|
44
|
-
const limit = Number(url.searchParams.get("limit") ?? 20);
|
|
45
|
-
const items = service.toApiList(service.search(q, { limit }));
|
|
46
|
-
sendJson(res, 200, { items });
|
|
47
|
-
} catch {
|
|
48
|
-
sendJson(res, 500, { error: "internal" });
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}));
|
|
52
|
-
|
|
53
|
-
return {
|
|
54
|
-
routes: 3,
|
|
55
|
-
dispose: () => {
|
|
56
|
-
for (const dispose of disposers) dispose();
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
}
|
|
1
|
+
import { URL } from "node:url";
|
|
2
|
+
|
|
3
|
+
function sendJson(res, status, payload) {
|
|
4
|
+
res.writeHead(status, { "Content-Type": "application/json; charset=utf-8" });
|
|
5
|
+
res.end(JSON.stringify(payload));
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function createApi(ctx, service) {
|
|
9
|
+
const disposers = [];
|
|
10
|
+
|
|
11
|
+
// /api/dsh-mneme prefix fallback → 404 JSON for unknown sub-paths
|
|
12
|
+
disposers.push(ctx.webServer.register({
|
|
13
|
+
kind: "prefix",
|
|
14
|
+
path: "/api/dsh-mneme",
|
|
15
|
+
handler(req, res) {
|
|
16
|
+
sendJson(res, 404, { error: "not-found" });
|
|
17
|
+
}
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
disposers.push(ctx.webServer.register({
|
|
21
|
+
kind: "exact",
|
|
22
|
+
path: "/api/dsh-mneme/list",
|
|
23
|
+
handler(req, res) {
|
|
24
|
+
try {
|
|
25
|
+
const url = new URL(req.url, "http://localhost");
|
|
26
|
+
const type = url.searchParams.get("type") ?? undefined;
|
|
27
|
+
const limit = Number(url.searchParams.get("limit") ?? 50);
|
|
28
|
+
const offset = Number(url.searchParams.get("offset") ?? 0);
|
|
29
|
+
const items = service.toApiList(service.list({ type, limit, offset }));
|
|
30
|
+
sendJson(res, 200, { items, total: service.count(type) });
|
|
31
|
+
} catch {
|
|
32
|
+
sendJson(res, 500, { error: "internal" });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
disposers.push(ctx.webServer.register({
|
|
38
|
+
kind: "exact",
|
|
39
|
+
path: "/api/dsh-mneme/search",
|
|
40
|
+
handler(req, res) {
|
|
41
|
+
try {
|
|
42
|
+
const url = new URL(req.url, "http://localhost");
|
|
43
|
+
const q = url.searchParams.get("q") ?? "";
|
|
44
|
+
const limit = Number(url.searchParams.get("limit") ?? 20);
|
|
45
|
+
const items = service.toApiList(service.search(q, { limit }));
|
|
46
|
+
sendJson(res, 200, { items });
|
|
47
|
+
} catch {
|
|
48
|
+
sendJson(res, 500, { error: "internal" });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
routes: 3,
|
|
55
|
+
dispose: () => {
|
|
56
|
+
for (const dispose of disposers) dispose();
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|