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/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
@@ -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.2",
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": {