@max-null/dsh-memory 0.2.1 → 0.3.7

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/dist/remote.js CHANGED
@@ -42,6 +42,7 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
42
42
  };
43
43
  import { TypertRemoteService, Remote } from '@deepseek-ai/dsh-typert-protocol';
44
44
  import { MemoryId } from "./engine.js";
45
+ import { SELF_DESCRIPTION } from "./self.js";
45
46
  /** Client-callable memory surface; registered as the `memory` Typert namespace. */
46
47
  let MemoryGateway = (() => {
47
48
  let _classSuper = TypertRemoteService;
@@ -49,18 +50,27 @@ let MemoryGateway = (() => {
49
50
  let _list_decorators;
50
51
  let _search_decorators;
51
52
  let _confirm_decorators;
53
+ let _setInjected_decorators;
52
54
  let _forget_decorators;
55
+ let _reload_decorators;
56
+ let _injectionPreview_decorators;
53
57
  return class MemoryGateway extends _classSuper {
54
58
  static {
55
59
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
56
60
  _list_decorators = [Remote('list')];
57
61
  _search_decorators = [Remote('search')];
58
62
  _confirm_decorators = [Remote('confirm')];
63
+ _setInjected_decorators = [Remote('setInjected')];
59
64
  _forget_decorators = [Remote('forget')];
65
+ _reload_decorators = [Remote('reload')];
66
+ _injectionPreview_decorators = [Remote('injectionPreview')];
60
67
  __esDecorate(this, null, _list_decorators, { kind: "method", name: "list", static: false, private: false, access: { has: obj => "list" in obj, get: obj => obj.list }, metadata: _metadata }, null, _instanceExtraInitializers);
61
68
  __esDecorate(this, null, _search_decorators, { kind: "method", name: "search", static: false, private: false, access: { has: obj => "search" in obj, get: obj => obj.search }, metadata: _metadata }, null, _instanceExtraInitializers);
62
69
  __esDecorate(this, null, _confirm_decorators, { kind: "method", name: "confirm", static: false, private: false, access: { has: obj => "confirm" in obj, get: obj => obj.confirm }, metadata: _metadata }, null, _instanceExtraInitializers);
70
+ __esDecorate(this, null, _setInjected_decorators, { kind: "method", name: "setInjected", static: false, private: false, access: { has: obj => "setInjected" in obj, get: obj => obj.setInjected }, metadata: _metadata }, null, _instanceExtraInitializers);
63
71
  __esDecorate(this, null, _forget_decorators, { kind: "method", name: "forget", static: false, private: false, access: { has: obj => "forget" in obj, get: obj => obj.forget }, metadata: _metadata }, null, _instanceExtraInitializers);
72
+ __esDecorate(this, null, _reload_decorators, { kind: "method", name: "reload", static: false, private: false, access: { has: obj => "reload" in obj, get: obj => obj.reload }, metadata: _metadata }, null, _instanceExtraInitializers);
73
+ __esDecorate(this, null, _injectionPreview_decorators, { kind: "method", name: "injectionPreview", static: false, private: false, access: { has: obj => "injectionPreview" in obj, get: obj => obj.injectionPreview }, metadata: _metadata }, null, _instanceExtraInitializers);
64
74
  if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
65
75
  }
66
76
  static inject = ['memory'];
@@ -70,21 +80,40 @@ let MemoryGateway = (() => {
70
80
  super(ctx, 'memoryRemote', { namespace: 'memory' });
71
81
  __runInitializers(this, _instanceExtraInitializers);
72
82
  }
73
- /** List every stored memory, optionally filtered by namespace or status. */
74
- list(filter) {
75
- return this.ctx.memory.list(filter);
83
+ /** List every stored memory, optionally filtered by namespace/status/injected and workspace cwd. */
84
+ async list(filter, cwd) {
85
+ return this.ctx.memory.list(filter, cwd);
76
86
  }
77
87
  /** Recall memories by keyword; deterministic literal matching. */
78
- search(query, filter) {
79
- return this.ctx.memory.search(query, filter);
88
+ async search(query, filter, cwd) {
89
+ return this.ctx.memory.search(query, filter, cwd);
80
90
  }
81
- /** Confirm a suggested memory so it becomes effective (`auto`). */
82
- confirm(id) {
83
- return this.ctx.memory.setStatus(MemoryId(id), 'auto');
91
+ /** Approve a suggested memory (review status `suggested` → `approved`); does not change injection. */
92
+ confirm(id, cwd) {
93
+ return this.ctx.memory.setStatus(MemoryId(id), 'approved', cwd);
94
+ }
95
+ /**
96
+ * Toggle the persistent-injection switch (`injected`) — the UI panel's
97
+ * 「常驻注入」 switch; does not change the review status.
98
+ */
99
+ setInjected(id, injected, cwd) {
100
+ return this.ctx.memory.setInjected(MemoryId(id), injected, cwd);
84
101
  }
85
102
  /** Delete one stored memory by id. */
86
- forget(id) {
87
- return this.ctx.memory.forget(MemoryId(id));
103
+ forget(id, cwd) {
104
+ return this.ctx.memory.forget(MemoryId(id), cwd);
105
+ }
106
+ /** Force-reload storage files (external edits; JsonStorageBackend has no watch). */
107
+ async reload(cwd) {
108
+ await this.ctx.memory.reload();
109
+ return this.ctx.memory.list({}, cwd);
110
+ }
111
+ /**
112
+ * 注入预览(0.3.5):开发者查看当前注入到 system prompt 的记忆内容
113
+ * ——self 自述 + 实际注入的 global approved+injected 记忆。
114
+ */
115
+ injectionPreview() {
116
+ return { self: SELF_DESCRIPTION, injected: this.ctx.memory.recallRecords() };
88
117
  }
89
118
  };
90
119
  })();
@@ -0,0 +1,11 @@
1
+ /**
2
+ * dsh-memory 自带面板的 HTTP 数据通道(0.3.6)。
3
+ * 背景:client 侧 `remote.memory` 命名空间依赖 Typert 构建期产物(独立
4
+ * npm 包生成不了——2026-08-16 设计文档结论),导致自带面板无法经 remote
5
+ * 取数。改走本端点(panels /ssid/api 同模式:host 插件注册 HTTP 路由,
6
+ * client fetch)——任何 profile 装 dsh-memory 即有面板,脱离 SSiD 也成立。
7
+ * 信任围栏同 /ssid/api:loopback / trustedHosts,拒绝 cross-site。
8
+ */
9
+ import type { Context } from '@deepseek-ai/cordis';
10
+ /** 挂载 /memory/api 路由(apply 调用)。 */
11
+ export declare function mountMemoryApi(ctx: Context): void;
package/dist/routes.js ADDED
@@ -0,0 +1,211 @@
1
+ import { createRequire } from 'node:module';
2
+ import { SELF_DESCRIPTION } from "./self.js";
3
+ const require = createRequire(import.meta.url);
4
+ /** Body size bound of one JSON request. */
5
+ const MAX_BODY_BYTES = 1 << 20;
6
+ class MemoryApiError extends Error {
7
+ code;
8
+ status;
9
+ constructor(code, message, status = 400) {
10
+ super(message);
11
+ this.code = code;
12
+ this.status = status;
13
+ this.name = 'MemoryApiError';
14
+ }
15
+ }
16
+ function writeJson(res, status, body) {
17
+ const r = res;
18
+ r.writeHead(status, { 'content-type': 'application/json; charset=utf-8' });
19
+ r.end(JSON.stringify(body));
20
+ }
21
+ function writeError(res, error) {
22
+ if (error instanceof MemoryApiError) {
23
+ writeJson(res, error.status, { ok: false, error: { code: error.code, message: error.message } });
24
+ return;
25
+ }
26
+ writeJson(res, 500, { ok: false, error: { code: 'internal', message: error instanceof Error ? error.message : String(error) } });
27
+ }
28
+ async function readJsonBody(req) {
29
+ const chunks = [];
30
+ let total = 0;
31
+ for await (const chunk of req) {
32
+ const buffer = Buffer.from(chunk);
33
+ total += buffer.length;
34
+ if (total > MAX_BODY_BYTES)
35
+ throw new MemoryApiError('bad-request', 'request body too large');
36
+ chunks.push(buffer);
37
+ }
38
+ const text = Buffer.concat(chunks).toString('utf8');
39
+ if (text.trim() === '')
40
+ return {};
41
+ try {
42
+ return JSON.parse(text);
43
+ }
44
+ catch {
45
+ throw new MemoryApiError('bad-request', 'request body is not valid JSON');
46
+ }
47
+ }
48
+ function header(headers, name) {
49
+ const value = headers[name];
50
+ return typeof value === 'string' ? value : undefined;
51
+ }
52
+ function parseAuthority(authority) {
53
+ try {
54
+ return new URL(`http://${authority}`);
55
+ }
56
+ catch {
57
+ return undefined;
58
+ }
59
+ }
60
+ function isLoopbackHostname(hostname) {
61
+ if (hostname === 'localhost' || hostname === '[::1]')
62
+ return true;
63
+ const parts = hostname.split('.');
64
+ return parts.length === 4
65
+ && parts[0] === '127'
66
+ && parts.every(part => /^\d{1,3}$/.test(part) && Number(part) <= 255);
67
+ }
68
+ /** Whether one request may reach the plugin routes (mirror of the /api gateway fence). */
69
+ function isTrusted(request, trustedHosts) {
70
+ const host = header(request.headers, 'host');
71
+ if (host === undefined)
72
+ return false;
73
+ const hostUrl = parseAuthority(host);
74
+ if (hostUrl === undefined)
75
+ return false;
76
+ const trusted = isLoopbackHostname(hostUrl.hostname)
77
+ || trustedHosts.some((entry) => {
78
+ const entryUrl = parseAuthority(entry);
79
+ return entryUrl !== undefined && entryUrl.host === hostUrl.host;
80
+ });
81
+ if (!trusted)
82
+ return false;
83
+ if (header(request.headers, 'sec-fetch-site') === 'cross-site')
84
+ return false;
85
+ const origin = header(request.headers, 'origin');
86
+ if (origin === undefined)
87
+ return true;
88
+ try {
89
+ return new URL(origin).host === hostUrl.host;
90
+ }
91
+ catch {
92
+ return false;
93
+ }
94
+ }
95
+ /** String-ish cwd payload guard. */
96
+ function cwdOf(payload) {
97
+ const value = payload?.cwd;
98
+ return typeof value === 'string' && value !== '' ? value : undefined;
99
+ }
100
+ /** 挂载 /memory/api 路由(apply 调用)。 */
101
+ export function mountMemoryApi(ctx) {
102
+ // webServer/webRuntime 由 index.ts 的 inject 声明(Context 类型未扩展,断言访问)
103
+ const webServer = ctx.webServer;
104
+ const webRuntime = ctx.webRuntime;
105
+ const api = {
106
+ 'list': (payload) => {
107
+ const memory = ctx.get('memory');
108
+ if (memory === undefined)
109
+ throw new MemoryApiError('service-unavailable', 'memory service unavailable', 503);
110
+ const record = payload;
111
+ const filter = record?.filter;
112
+ return memory.list({
113
+ ...filter?.namespace !== undefined && typeof filter.namespace === 'string' ? { namespace: filter.namespace } : {},
114
+ ...filter?.status !== undefined && typeof filter.status === 'string' ? { status: filter.status } : {},
115
+ ...filter?.injected !== undefined && typeof filter.injected === 'boolean' ? { injected: filter.injected } : {},
116
+ }, cwdOf(record));
117
+ },
118
+ 'reload': async (payload) => {
119
+ const memory = ctx.get('memory');
120
+ if (memory === undefined)
121
+ throw new MemoryApiError('service-unavailable', 'memory service unavailable', 503);
122
+ await memory.reload();
123
+ return memory.list({}, cwdOf(payload));
124
+ },
125
+ 'confirm': (payload) => {
126
+ const memory = ctx.get('memory');
127
+ if (memory === undefined)
128
+ throw new MemoryApiError('service-unavailable', 'memory service unavailable', 503);
129
+ const record = payload;
130
+ if (typeof record?.id !== 'string')
131
+ throw new MemoryApiError('bad-request', 'missing or invalid "id"');
132
+ return memory.setStatus(record.id, 'approved', cwdOf(record));
133
+ },
134
+ 'forget': (payload) => {
135
+ const memory = ctx.get('memory');
136
+ if (memory === undefined)
137
+ throw new MemoryApiError('service-unavailable', 'memory service unavailable', 503);
138
+ const record = payload;
139
+ if (typeof record?.id !== 'string')
140
+ throw new MemoryApiError('bad-request', 'missing or invalid "id"');
141
+ return memory.forget(record.id, cwdOf(record));
142
+ },
143
+ 'setInjected': (payload) => {
144
+ const memory = ctx.get('memory');
145
+ if (memory === undefined)
146
+ throw new MemoryApiError('service-unavailable', 'memory service unavailable', 503);
147
+ const record = payload;
148
+ if (typeof record?.id !== 'string')
149
+ throw new MemoryApiError('bad-request', 'missing or invalid "id"');
150
+ if (typeof record?.injected !== 'boolean')
151
+ throw new MemoryApiError('bad-request', 'missing or invalid "injected"');
152
+ return memory.setInjected(record.id, record.injected, cwdOf(record));
153
+ },
154
+ 'update': (payload) => {
155
+ const memory = ctx.get('memory');
156
+ if (memory === undefined)
157
+ throw new MemoryApiError('service-unavailable', 'memory service unavailable', 503);
158
+ const record = payload;
159
+ if (typeof record?.id !== 'string')
160
+ throw new MemoryApiError('bad-request', 'missing or invalid "id"');
161
+ if (record.content !== undefined && typeof record.content !== 'string')
162
+ throw new MemoryApiError('bad-request', 'invalid "content"');
163
+ if (record.keywords !== undefined && !(Array.isArray(record.keywords) && record.keywords.every(k => typeof k === 'string'))) {
164
+ throw new MemoryApiError('bad-request', 'invalid "keywords"');
165
+ }
166
+ return memory.update(record.id, {
167
+ ...record.content === undefined ? {} : { content: record.content },
168
+ ...record.keywords === undefined ? {} : { keywords: record.keywords },
169
+ }, cwdOf(record));
170
+ },
171
+ 'injectionPreview': () => {
172
+ const memory = ctx.get('memory');
173
+ if (memory === undefined)
174
+ throw new MemoryApiError('service-unavailable', 'memory service unavailable', 503);
175
+ return { self: SELF_DESCRIPTION, injected: memory.recallRecords() };
176
+ },
177
+ };
178
+ const fence = (req) => isTrusted(req, webRuntime.trustedHosts);
179
+ ctx.effect(() => webServer.register({
180
+ kind: 'prefix',
181
+ path: '/memory/api',
182
+ handler: async (req, res) => {
183
+ const request = req;
184
+ if (!fence(request)) {
185
+ writeJson(res, 403, { ok: false, error: { code: 'forbidden', message: 'forbidden' } });
186
+ return;
187
+ }
188
+ if (request.method !== 'POST') {
189
+ writeJson(res, 405, { ok: false, error: { code: 'method-error', message: 'method not allowed' } });
190
+ return;
191
+ }
192
+ const pathname = new URL(request.url ?? '/', 'http://dsh.internal').pathname;
193
+ const method = pathname.startsWith('/memory/api/') ? pathname.slice('/memory/api/'.length) : undefined;
194
+ if (method === undefined || method.includes('/')) {
195
+ writeError(res, new MemoryApiError('not-found', 'unknown memory API method', 404));
196
+ return;
197
+ }
198
+ try {
199
+ const payload = await readJsonBody(req);
200
+ const handler = api[method];
201
+ if (handler === undefined) {
202
+ throw new MemoryApiError('not-found', `unknown memory API method "${method}"`, 404);
203
+ }
204
+ writeJson(res, 200, { ok: true, value: await handler(payload) });
205
+ }
206
+ catch (error) {
207
+ writeError(res, error);
208
+ }
209
+ },
210
+ }), '@max-null/dsh-memory: /memory/api routes');
211
+ }
package/dist/self.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare const SELF_DESCRIPTION: string;
package/dist/self.js ADDED
@@ -0,0 +1,26 @@
1
+ /**
2
+ * 记忆机制自述(memory:self 注入内容 + 面板「注入预览」共用)。
3
+ * 0.3.2 起:LLM 每轮知道本环境有记忆机制;不落用户存储,随发版更新,
4
+ * 版本号动态读取 package.json("只跟随 dsh-memory 组件发版变动")。
5
+ */
6
+ import { createRequire } from 'node:module';
7
+ const SELF_VERSION = (() => {
8
+ try {
9
+ // dist/index.js 位于包根 package.json 同级(../package.json)
10
+ const require = createRequire(import.meta.url);
11
+ const pkg = require('../package.json');
12
+ return pkg.version ?? 'unknown';
13
+ }
14
+ catch {
15
+ return 'unknown';
16
+ }
17
+ })();
18
+ export const SELF_DESCRIPTION = `[记忆系统自述] 当前环境内置跨会话记忆服务(dsh-memory v${SELF_VERSION})。`
19
+ + '工作机制:①记忆以明文 JSON 存储(global→$DSH_HOME/storages、工作区→会话工作区 .dsh/storages 随 git 分享);'
20
+ + '②模型写入即 suggested(待审核),仅人工审核(memory_confirm)后才 approved;'
21
+ + '③每轮注入 system prompt 的只有「global 的 approved + injected 开关打开」的记忆(常驻注入由用户控制,审核≠注入);'
22
+ + '④工作区记忆按会话工作区路由,靠 memory_search 检索(含待审核条目,可先评估再引用);'
23
+ + '⑤修正过时内容用 memory_update(改动重置待审核,注入开关保留)。'
24
+ + `可用记忆工具共 6 个:memory_save / memory_list / memory_search / memory_confirm / memory_forget / memory_update。`
25
+ + '使用建议:相关历史上下文先 memory_search 检索再引用;新习惯/约定用 memory_save 写入(落在待审核)。'
26
+ + '(中/英文同义:This environment has a cross-session memory service; write suggestions via memory_save, retrieve via memory_search, and expect human review before approved.)';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@max-null/dsh-memory",
3
- "version": "0.2.1",
3
+ "version": "0.3.7",
4
4
  "description": "Cross-session plaintext memory plugin for the DeepSeek Harness — deterministic BM25 recall, human-owned, no embeddings",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -13,7 +13,9 @@
13
13
  ".": {
14
14
  "types": "./dist/index.d.ts",
15
15
  "default": "./dist/index.js"
16
- }
16
+ },
17
+ "./client": "./client.js",
18
+ "./package.json": "./package.json"
17
19
  },
18
20
  "files": [
19
21
  "dist",
@@ -22,6 +24,13 @@
22
24
  "dsh": {
23
25
  "bundle": {
24
26
  "patch": "./cordis.patch.yml"
27
+ },
28
+ "client": {
29
+ "inject": [
30
+ "@deepseek-ai/dsh-client-runtime",
31
+ "@deepseek-ai/dsh-client-locale"
32
+ ],
33
+ "platform": "web"
25
34
  }
26
35
  },
27
36
  "keywords": [
@@ -39,7 +48,7 @@
39
48
  "url": "git+https://github.com/Max-Null/dsh-memory.git"
40
49
  },
41
50
  "scripts": {
42
- "build": "tsc -p tsconfig.json",
51
+ "build": "tsc -p tsconfig.json && node build-client.mjs",
43
52
  "typecheck": "tsc --noEmit -p tsconfig.json",
44
53
  "test": "vitest run",
45
54
  "prepublishOnly": "npm run build"