sage-mem 0.4.2 → 0.4.3
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/NOTICE +3 -0
- package/lib/client.js +1357 -9
- package/lib/index.js +68 -1
- package/lib/typert.host.js +39 -0
- package/package.json +2 -2
package/lib/index.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol'
|
|
17
17
|
import { homedir } from 'node:os'
|
|
18
|
-
import { readFile, readdir, writeFile, unlink } from 'node:fs/promises'
|
|
18
|
+
import { readFile, readdir, writeFile, unlink, stat } from 'node:fs/promises'
|
|
19
19
|
import { join, basename } from 'node:path'
|
|
20
20
|
|
|
21
21
|
const MEMORY_DIR = process.env.SAGE_MEM_DIR || join(homedir(), '.sage-mem', 'memory')
|
|
@@ -125,6 +125,8 @@ function markRemote(cls, method, exportName) {
|
|
|
125
125
|
export class MemoryGateway extends TypertRemoteService {
|
|
126
126
|
constructor(ctx) {
|
|
127
127
|
super(ctx, 'memory')
|
|
128
|
+
// 一并挂载星图 remote(同一记忆目录,纯只读)—— starmap.* 服务
|
|
129
|
+
new StarmapGateway(ctx)
|
|
128
130
|
|
|
129
131
|
// 按问题检索注入:system-prompt/assemble 是异步 waterfall,监听器可 await。
|
|
130
132
|
// 从 agent.session 拿当前 user message,扫 memory 目录按双字匹配选相关
|
|
@@ -209,4 +211,69 @@ markRemote(MemoryGateway, 'readFile', 'readFile')
|
|
|
209
211
|
markRemote(MemoryGateway, 'writeFile', 'writeFile')
|
|
210
212
|
markRemote(MemoryGateway, 'deleteFile', 'deleteFile')
|
|
211
213
|
|
|
214
|
+
/** 提取正文第一个 `# ` 标题作为展示标题。 */
|
|
215
|
+
function extractTitle(content) {
|
|
216
|
+
const m = content.match(/^---\r?\n[\s\S]*?\r?\n---/)
|
|
217
|
+
const body = m ? content.slice(m[0].length) : content
|
|
218
|
+
const h1 = body.match(/^#\s+(.+)$/m)
|
|
219
|
+
return h1 ? h1[1].trim() : ''
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** 扫记忆目录,读全部 .md(跳过索引与 session-log),带体积与修改时间。 */
|
|
223
|
+
async function scanStars() {
|
|
224
|
+
let entries
|
|
225
|
+
try { entries = await readdir(MEMORY_DIR, { withFileTypes: true }) } catch { return [] }
|
|
226
|
+
const names = entries
|
|
227
|
+
.filter(e => e.isFile() && e.name.endsWith('.md') && e.name !== 'MEMORY.md' && e.name !== 'session-log.md')
|
|
228
|
+
.map(e => e.name)
|
|
229
|
+
const loaded = await Promise.all(names.map(async (name) => {
|
|
230
|
+
try {
|
|
231
|
+
const full = join(MEMORY_DIR, name)
|
|
232
|
+
const [content, info] = await Promise.all([readFile(full, 'utf8'), stat(full)])
|
|
233
|
+
return { name, content, bytes: info.size, mtimeMs: info.mtimeMs }
|
|
234
|
+
} catch { return null }
|
|
235
|
+
}))
|
|
236
|
+
return loaded.filter(Boolean)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* 记忆星图(host 半)—— 只读扫描记忆目录,把每条记忆解析成一颗「星」。
|
|
241
|
+
* 通过 TypertRemoteService 暴露两个 remote 方法给浏览器半:
|
|
242
|
+
* - starmap.listStars() 全部星(元数据,不含正文)
|
|
243
|
+
* - starmap.readFile(n) 单条记忆全文(文件名白名单校验)
|
|
244
|
+
*/
|
|
245
|
+
export class StarmapGateway extends TypertRemoteService {
|
|
246
|
+
constructor(ctx) { super(ctx, 'starmap') }
|
|
247
|
+
|
|
248
|
+
async listStars() {
|
|
249
|
+
const files = await scanStars()
|
|
250
|
+
const stars = files.map(f => {
|
|
251
|
+
const meta = parseFrontmatter(f.content)
|
|
252
|
+
const title = extractTitle(f.content)
|
|
253
|
+
return {
|
|
254
|
+
file: f.name,
|
|
255
|
+
kind: meta.type || 'special',
|
|
256
|
+
title: title || meta.name || (meta.description ? meta.description.slice(0, 24) : f.name.replace(/\.md$/, '')),
|
|
257
|
+
desc: meta.description || '',
|
|
258
|
+
bytes: f.bytes,
|
|
259
|
+
mtimeMs: f.mtimeMs,
|
|
260
|
+
}
|
|
261
|
+
})
|
|
262
|
+
return { count: stars.length, stars }
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async readFile(name) {
|
|
266
|
+
const raw = String(name || '')
|
|
267
|
+
const safe = basename(raw)
|
|
268
|
+
if (!safe || safe !== raw || !safe.endsWith('.md') || safe === 'MEMORY.md') {
|
|
269
|
+
throw new Error('sage-starmap: invalid file name')
|
|
270
|
+
}
|
|
271
|
+
const content = await readFile(join(MEMORY_DIR, safe), 'utf8')
|
|
272
|
+
return { name: safe, content }
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
markRemote(StarmapGateway, 'listStars', 'listStars')
|
|
277
|
+
markRemote(StarmapGateway, 'readFile', 'readFile')
|
|
278
|
+
|
|
212
279
|
export default MemoryGateway
|
package/lib/typert.host.js
CHANGED
|
@@ -9,6 +9,23 @@ import { z } from 'zod'
|
|
|
9
9
|
const fileNameSchema = z.string()
|
|
10
10
|
const fileContentSchema = z.string()
|
|
11
11
|
|
|
12
|
+
const starmapStarSchema = z.object({
|
|
13
|
+
'file': z.string().readonly(),
|
|
14
|
+
'kind': z.string().readonly(),
|
|
15
|
+
'title': z.string().readonly(),
|
|
16
|
+
'desc': z.string().readonly(),
|
|
17
|
+
'bytes': z.number().readonly(),
|
|
18
|
+
'mtimeMs': z.number().readonly(),
|
|
19
|
+
}).readonly()
|
|
20
|
+
const starmapListSchema = z.object({
|
|
21
|
+
'count': z.number().readonly(),
|
|
22
|
+
'stars': z.array(starmapStarSchema).readonly(),
|
|
23
|
+
}).readonly()
|
|
24
|
+
const starmapReadSchema = z.object({
|
|
25
|
+
'name': z.string().readonly(),
|
|
26
|
+
'content': z.string().readonly(),
|
|
27
|
+
}).readonly()
|
|
28
|
+
|
|
12
29
|
const fileSchema = z.object({
|
|
13
30
|
'file': z.string().readonly(),
|
|
14
31
|
'type': z.string().readonly(),
|
|
@@ -81,6 +98,28 @@ export const TYPERT = {
|
|
|
81
98
|
result: { mode: 'strict', typeSymbol: 'sage-mem/types#DeleteResult', schema: deleteResultSchema },
|
|
82
99
|
sourceLocation: { "file": "workspace/sage-mem/lib/index.js", "line": 1, "column": 1 },
|
|
83
100
|
},
|
|
101
|
+
{
|
|
102
|
+
id: 'sage-mem#starmap/listStars',
|
|
103
|
+
service: 'starmap',
|
|
104
|
+
namespace: 'starmap',
|
|
105
|
+
method: 'listStars',
|
|
106
|
+
invocation: { kind: 'direct' },
|
|
107
|
+
parameters: [],
|
|
108
|
+
result: { mode: 'strict', typeSymbol: 'sage-mem/types#StarmapList', schema: starmapListSchema },
|
|
109
|
+
sourceLocation: { "file": "workspace/sage-mem/lib/index.js", "line": 1, "column": 1 },
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: 'sage-mem#starmap/readFile',
|
|
113
|
+
service: 'starmap',
|
|
114
|
+
namespace: 'starmap',
|
|
115
|
+
method: 'readFile',
|
|
116
|
+
invocation: { kind: 'direct' },
|
|
117
|
+
parameters: [
|
|
118
|
+
{ name: 'name', wire: 'name', source: 'json', codec: { mode: 'strict', typeSymbol: 'sage-mem/types#FileName', schema: fileNameSchema } },
|
|
119
|
+
],
|
|
120
|
+
result: { mode: 'strict', typeSymbol: 'sage-mem/types#StarmapRead', schema: starmapReadSchema },
|
|
121
|
+
sourceLocation: { "file": "workspace/sage-mem/lib/index.js", "line": 1, "column": 1 },
|
|
122
|
+
},
|
|
84
123
|
],
|
|
85
124
|
model: {
|
|
86
125
|
"services": [],
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sage-mem",
|
|
3
|
-
"description": "File-based memory plugin for DeepSeek Harness (DSH) — cross-session memory in transparent markdown files",
|
|
4
|
-
"version": "0.4.
|
|
3
|
+
"description": "File-based memory plugin for DeepSeek Harness (DSH) — cross-session memory in transparent markdown files, with a memory star-map view",
|
|
4
|
+
"version": "0.4.3",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|