dsh-memory-graph-chiyo 0.0.0-stage → 0.7.0

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/docs/API.md ADDED
@@ -0,0 +1,124 @@
1
+ # API 速查
2
+
3
+ 所有入口都从包根导入:
4
+
5
+ ```js
6
+ import { createMemory, write, ... } from 'dsh-memory-graph-chiyo';
7
+ const mem = createMemory({ dir: './memory-data', options: {} });
8
+ ```
9
+
10
+ ## 记忆图
11
+
12
+ ### `mem.write(scope, { nodes, messages?, allowSensitive?, sensitiveAllow? })`
13
+ 写节点。`fact`/`plan` 必须有锚点;传了 `messages` 时锚点必须能在原文里找到。
14
+ - `nodes[].kind`: `fact` | `impression` | `plan`
15
+ - `nodes[].anchors`: `[{ quote, source?, at? }]`
16
+ - `nodes[].relations`: `[{ type, targetId, weight? }]`,类型见 `REL_TYPES`
17
+ - `nodes[].idempotencyKey` / `mentionKey`:幂等去重
18
+ - `nodes[].validDays` / `validTo`:plan 专用
19
+ 返回 `{ written, merged, total }`。
20
+
21
+ ### `mem.recall(scope, { query, limit, includeCold, includeArchived, hops })`
22
+ 召回。`hops=1` 沿关系边扩展(出边 + 入边)。命中会提升 `recallCount` 与重要度。
23
+
24
+ ### `mem.recallAcross(scopes, { query, limit, perScopeLimit, includeCold })`
25
+ 多 scope 合并召回,结果带 `scope` 字段。
26
+
27
+ ### `mem.load(scope)` / `mem.tickLayers(scope, { now, apply })`
28
+ 读全部节点(last-wins)/ 推进层状态。
29
+
30
+ ### `mem.tickPlans(scope)` / `mem.setPlanStatus(scope, id, status, reason)`
31
+ 计划过期与手动改状态。
32
+
33
+ ## 即时记忆
34
+
35
+ ```js
36
+ mem.writeInstant(scope, { text, topicKey, eventKey, personKey? });
37
+ mem.tickInstant(scope, { now, apply });
38
+ mem.reviveInstant(scope, { topicKey, eventKey, personKey? });
39
+ ```
40
+
41
+ ## 共享记忆
42
+
43
+ ```js
44
+ const e = mem.proposeShared({ text, sourceScope, judgment?, dependsOn? }); // 默认每天 3 条
45
+ mem.approveShared(e.id, { by });
46
+ mem.revokeShared(e.id, { by, reason, cascade: true });
47
+ mem.listShared({ status });
48
+ mem.recallShared({ query, limit }); // 只返回 approved
49
+ mem.shared.audit({ limit });
50
+ ```
51
+
52
+ ## 抽取 / 装配
53
+
54
+ ```js
55
+ mem.heuristicExtract(messages, { speaker });
56
+ mem.extractAndWrite(scope, messages, { speaker, max });
57
+ mem.buildExtractionPrompt(messages); // 给大模型用
58
+ mem.assembleContext({ scopes, query, tokenBudget });
59
+ // → { text, tokens, used, total }
60
+ ```
61
+
62
+ ## 压缩
63
+
64
+ ```js
65
+ const comp = mem.compactor({ windowTokens: 128000, thresholdRatio: 0.3, keepTurns: 10 });
66
+ const r = await comp.compact(events);
67
+ comp.expand(r); // 取回被折叠原文
68
+ comp.stats(); // 每次压缩放掉多少
69
+ ```
70
+
71
+ ## 整理 / 体检 / 计划任务
72
+
73
+ ```js
74
+ mem.consolidate(scope, { apply });
75
+ mem.healthReport({ scope }); // 六项指标
76
+ mem.nightly(['user:alice'], { apply: true });
77
+ mem.tickAll({ scope, apply: true });
78
+ ```
79
+
80
+ ## 规则 / 人物 / 时间线 / 星图
81
+
82
+ ```js
83
+ extractRules(text, { speaker }); // 返回可写入的规则节点
84
+ mem.person.upsert({ name, aliases, note });
85
+ mem.person.proposeMerge(a, b); mem.person.approveMerge(id, { by });
86
+ mem.episode({ scope, from, to });
87
+ mem.missedScan({ scope, messages });
88
+ mem.starMap(nodes, { title }); // SVG 字符串
89
+ mem.mermaid(nodes); // Mermaid 字符串
90
+ ```
91
+
92
+ ## 快照
93
+
94
+ ```js
95
+ const bundle = mem.exportBundle({ note: 'before cleanup' });
96
+ saveBundle(bundle, './backup.json');
97
+ importBundle(loadBundle('./backup.json'), './memory-data', { overwrite: true });
98
+ ```
99
+
100
+ ## 关系衰减
101
+
102
+ ```js
103
+ mem.decayRelations(scope, { halfLifeDays: 180, minWeight: 0.05, apply: true });
104
+ ```
105
+
106
+ ## 人格(可选,角色扮演层)
107
+
108
+ ```js
109
+ mem.persona.get();
110
+ mem.persona.tune('initiative', 0.05, { by: 'owner', reason: '…' }); // ≤0.05/次,1 次/天,漂移 ≤0.15
111
+ mem.persona.summary();
112
+ mem.persona.revert();
113
+ ```
114
+
115
+ ## 命令行
116
+
117
+ ```bash
118
+ npx memory-graph health --dir ./data --scope user:alice
119
+ npx memory-graph recall --dir ./data --scope user:alice --query 下雨
120
+ npx memory-graph recall-all --dir ./data --scopes user:alice,agent:chiyo --query 下雨
121
+ npx memory-graph nightly --dir ./data --scopes user:alice --apply
122
+ npx memory-graph shared-list --dir ./data
123
+ npx memory-graph shared-approve --dir ./data --id s_xxx --by owner
124
+ ```
package/index.d.ts ADDED
@@ -0,0 +1,337 @@
1
+ // Type definitions for dsh-memory-graph-chiyo 0.6
2
+ // 手写声明,追求"能用、不骗人":不确定的结构用宽松类型。
3
+
4
+ export type Scope = string;
5
+ export type Kind = 'fact' | 'impression' | 'plan';
6
+ export type Category = 'preference' | 'experience' | 'relationship' | 'plan' | 'emotion' | 'topic';
7
+ export type Layer = 'instant' | 'short' | 'long' | 'cold' | 'permanent';
8
+ export type RelationType = 'person' | 'relationship' | 'because' | 'derivedFrom' | 'supports' | 'contradicts';
9
+ export type PlanStatus = 'active' | 'done' | 'abandoned' | 'expired';
10
+
11
+ export interface Anchor {
12
+ quote: string;
13
+ source?: string;
14
+ at?: number;
15
+ }
16
+
17
+ export interface Relation {
18
+ type: RelationType;
19
+ targetId: string;
20
+ weight?: number;
21
+ }
22
+
23
+ export interface MemoryNode {
24
+ id: string;
25
+ scope: Scope;
26
+ kind: Kind | 'instant';
27
+ text: string;
28
+ anchors: Anchor[];
29
+ relations?: Relation[];
30
+ category?: Category | string;
31
+ layer?: Layer;
32
+ cluster?: string;
33
+ status?: PlanStatus;
34
+ validTo?: number;
35
+ mentionCount?: number;
36
+ recallCount?: number;
37
+ importance?: number;
38
+ idempotencyKey?: string;
39
+ mentionKey?: string;
40
+ superseded?: boolean;
41
+ supersededBy?: string;
42
+ supersedes?: string;
43
+ archived?: boolean;
44
+ createdAt: number;
45
+ updatedAt: number;
46
+ lastTouchedAt?: number;
47
+ [key: string]: unknown;
48
+ }
49
+
50
+ export interface WriteInput {
51
+ kind: Kind;
52
+ text: string;
53
+ anchors?: Anchor[];
54
+ relations?: Relation[];
55
+ category?: Category | string;
56
+ cluster?: string;
57
+ idempotencyKey?: string;
58
+ mentionKey?: string;
59
+ validDays?: number;
60
+ validTo?: number;
61
+ }
62
+
63
+ export interface WriteOptions {
64
+ nodes: WriteInput[];
65
+ writer?: string;
66
+ messages?: Array<string | { text?: string; content?: string }> | null;
67
+ allowSensitive?: boolean;
68
+ sensitiveAllow?: string[];
69
+ }
70
+
71
+ export interface WriteResult {
72
+ written: MemoryNode[];
73
+ merged: Array<{ id: string; text: string; mentionCount: number }>;
74
+ total: number;
75
+ }
76
+
77
+ export interface RecallOptions {
78
+ query?: string;
79
+ limit?: number;
80
+ includeCold?: boolean;
81
+ includeArchived?: boolean;
82
+ includeSuperseded?: boolean;
83
+ hops?: number;
84
+ }
85
+
86
+ export interface RecalledNode extends MemoryNode {
87
+ score: number;
88
+ via?: string;
89
+ relation?: RelationType;
90
+ }
91
+
92
+ export interface AssembleOptions {
93
+ scopes: Scope[];
94
+ query?: string;
95
+ tokenBudget?: number;
96
+ includeShared?: boolean;
97
+ includePlans?: boolean;
98
+ limit?: number;
99
+ }
100
+
101
+ export interface AssembleResult {
102
+ text: string;
103
+ tokens: number;
104
+ used: string[];
105
+ total: number;
106
+ }
107
+
108
+ export interface CompactOptions {
109
+ windowTokens?: number;
110
+ thresholdRatio?: number;
111
+ keepTurns?: number;
112
+ pruner?: { thresholdChars?: number; headChars?: number; tailChars?: number };
113
+ summaryMaxChars?: number;
114
+ summarizer?: (events: unknown[], ctx: { estimateTokens: (s: string) => number; maxChars: number }) => string | Promise<string>;
115
+ }
116
+
117
+ export interface CompactResult {
118
+ compacted: boolean;
119
+ events: unknown[];
120
+ beforeTokens: number;
121
+ afterTokens: number;
122
+ shadowed?: unknown[];
123
+ anchors?: string[];
124
+ summary?: string;
125
+ pruned?: number;
126
+ savedChars?: number;
127
+ note?: string;
128
+ }
129
+
130
+ export interface SharedEntry {
131
+ id: string;
132
+ text: string;
133
+ judgment?: string;
134
+ sourceScope: string;
135
+ status: 'proposed' | 'approved' | 'revoked';
136
+ dependsOn?: string[];
137
+ proposedAt: number;
138
+ updatedAt: number;
139
+ [key: string]: unknown;
140
+ }
141
+
142
+ export interface HealthCheck {
143
+ name: string;
144
+ ok: boolean;
145
+ value: number;
146
+ detail: string;
147
+ }
148
+
149
+ export interface HealthReport {
150
+ scope: Scope;
151
+ at: string;
152
+ total: number;
153
+ live: number;
154
+ archived: number;
155
+ byLayer: Record<string, number>;
156
+ byKind: Record<string, number>;
157
+ checks: HealthCheck[];
158
+ ok: boolean;
159
+ failed: string[];
160
+ }
161
+
162
+ export interface Memory {
163
+ store: unknown;
164
+ graph: unknown;
165
+ instant: unknown;
166
+ shared: unknown;
167
+ person: unknown;
168
+ persona: unknown;
169
+
170
+ write(scope: Scope, input: WriteOptions): WriteResult;
171
+ recall(scope: Scope, opts?: RecallOptions): RecalledNode[];
172
+ recallAcross(scopes: Scope[], opts?: RecallOptions & { perScopeLimit?: number }): RecalledNode[];
173
+ load(scope: Scope): MemoryNode[];
174
+
175
+ tickLayers(scope: Scope, opts?: { now?: number; apply?: boolean }): Array<{ id: string; from: string; to: string }>;
176
+ tickPlans(scope: Scope, opts?: { now?: number; apply?: boolean }): Array<{ id: string; text: string }>;
177
+ setPlanStatus(scope: Scope, id: string, status: PlanStatus, reason?: string): MemoryNode;
178
+
179
+ writeInstant(scope: Scope, input: { text: string; topicKey: string; eventKey: string; personKey?: string }): MemoryNode;
180
+ tickInstant(scope: Scope, opts?: { now?: number; apply?: boolean }): Array<{ id: string; from: string; to: string }>;
181
+ reviveInstant(scope: Scope, input: { topicKey: string; eventKey: string; personKey?: string }): unknown;
182
+
183
+ proposeShared(input: { text: string; sourceScope: string; judgment?: string; by?: string; dependsOn?: string[] }): SharedEntry;
184
+ approveShared(id: string, opts?: { by?: string }): SharedEntry;
185
+ revokeShared(id: string, opts?: { by?: string; reason?: string; cascade?: boolean }): SharedEntry;
186
+ listShared(opts?: { status?: string }): SharedEntry[];
187
+ recallShared(opts?: { query?: string; limit?: number }): Array<SharedEntry & { score: number }>;
188
+
189
+ heuristicExtract(messages: unknown[], opts?: { speaker?: 'owner' | 'self'; max?: number }): WriteInput[];
190
+ extractAndWrite(scope: Scope, messages: unknown[], opts?: { speaker?: 'owner' | 'self'; max?: number }): { written: MemoryNode[]; merged: unknown[]; skipped: unknown[] };
191
+ buildExtractionPrompt(messages: unknown[]): string;
192
+ assembleContext(opts: AssembleOptions): AssembleResult;
193
+
194
+ exportBundle(opts?: { dir?: string; note?: string }): unknown;
195
+ importBundle(bundle: unknown, opts?: { dir?: string; overwrite?: boolean }): { written: number; skipped: number; dir: string };
196
+ decayRelations(scope: Scope, opts?: { now?: number; halfLifeDays?: number; minWeight?: number; apply?: boolean }): unknown;
197
+
198
+ scanConflicts(scope: Scope, opts?: { apply?: boolean; minSubjectSim?: number }): Array<{ oldId: string; newId: string; oldText: string; newText: string }>;
199
+ scanStore(opts?: { dir?: string }): unknown;
200
+ repairStore(opts?: { dir?: string; apply?: boolean }): unknown;
201
+
202
+ extractRules(text: string, opts?: { speaker?: 'owner' | 'self' }): WriteInput[];
203
+ episode(opts: { scope: Scope; from?: number; to?: number }): unknown;
204
+ missedScan(opts: { scope: Scope; messages: unknown[] }): unknown;
205
+ healthReport(opts: { scope: Scope; now?: number }): HealthReport;
206
+ starMap(nodes?: MemoryNode[], opts?: { width?: number; height?: number; title?: string; showLabels?: boolean }): string;
207
+ mermaid(nodes?: MemoryNode[], opts?: { maxNodes?: number }): string;
208
+ scanSensitive(text: string, opts?: { allow?: string[] }): Array<{ type: string; label: string }>;
209
+
210
+ consolidate(scope: Scope, opts?: { apply?: boolean }): unknown;
211
+ health(scope: Scope, opts?: { now?: number }): unknown;
212
+ tickAll(opts: { scope?: Scope; now?: number; apply?: boolean }): unknown;
213
+ nightly(scopes: Scope[], opts?: { now?: number; apply?: boolean }): unknown;
214
+
215
+ compactor(opts?: CompactOptions): { compact(events: unknown[]): Promise<CompactResult>; expand(record: CompactResult | unknown): unknown[]; stats(): unknown[] };
216
+ }
217
+
218
+ export function createMemory(config?: { dir?: string; options?: Record<string, unknown>; clock?: () => number }): Memory;
219
+ export default createMemory;
220
+
221
+ export function estimateTokens(text: string): number;
222
+ export function pruneToolResults(events: unknown[], options?: Record<string, unknown>): { events: unknown[]; pruned: number; savedChars: number };
223
+ export function createCompactor(options?: CompactOptions): Memory['compactor'] extends (o?: CompactOptions) => infer R ? R : never;
224
+
225
+ export function scanSensitive(text: string, opts?: { allow?: string[] }): Array<{ type: string; label: string }>;
226
+ export function assertNotSensitive(text: string, opts?: { allow?: string[] }): true;
227
+ export function findMissingAnchors(anchors: Array<{ quote: string }>, messages: unknown[]): string[];
228
+ export function quoteExists(quote: string, messages: unknown[]): boolean;
229
+ export function extractAnchors(text: string, messages: unknown[], opts?: { max?: number; minLen?: number }): Anchor[];
230
+
231
+ export function heuristicExtract(messages: unknown[], opts?: { speaker?: 'owner' | 'self'; max?: number }): WriteInput[];
232
+ export function extractAndWrite(memory: Memory, scope: Scope, messages: unknown[], opts?: Record<string, unknown>): unknown;
233
+ export function buildExtractionPrompt(messages: unknown[]): string;
234
+ export function assembleContext(memory: Memory, opts: AssembleOptions): AssembleResult;
235
+
236
+ export function exportBundle(dir: string, opts?: { note?: string }): unknown;
237
+ export function importBundle(bundle: unknown, dir: string, opts?: { overwrite?: boolean }): unknown;
238
+ export function saveBundle(bundle: unknown, file: string): string;
239
+ export function loadBundle(file: string): unknown;
240
+
241
+ export function scanConflicts(graph: unknown, scope: Scope, opts?: Record<string, unknown>): unknown;
242
+ export function findConflict(existing: MemoryNode[], candidate: { text: string; kind: string; category?: string }, opts?: { minSubjectSim?: number }): unknown;
243
+ export function polarityOf(text: string): 'pos' | 'neg' | 'neutral';
244
+ export function scanStore(dir: string): unknown;
245
+ export function repairStore(dir: string, opts?: { apply?: boolean }): unknown;
246
+
247
+ export function renderStarMap(nodes: MemoryNode[], opts?: Record<string, unknown>): string;
248
+ export function renderMermaid(nodes: MemoryNode[], opts?: Record<string, unknown>): string;
249
+ export function healthReport(memory: Memory, scope: Scope, opts?: Record<string, unknown>): HealthReport;
250
+
251
+ export function extractRules(text: string, opts?: { speaker?: 'owner' | 'self'; at?: number; max?: number }): WriteInput[];
252
+ export function classifyRule(text: string, opts?: { speaker?: 'owner' | 'self' }): string | null;
253
+
254
+ export function consolidate(graph: unknown, scope: Scope, opts?: { apply?: boolean; now?: number }): unknown;
255
+ export function health(graph: unknown, scope: Scope, opts?: { now?: number }): unknown;
256
+ export function tickAll(memory: Memory, opts?: { scope?: Scope; now?: number; apply?: boolean }): unknown;
257
+ export function nightly(memory: Memory, opts?: { scopes?: Scope[]; now?: number; apply?: boolean }): unknown;
258
+
259
+ export function buildEpisode(memory: Memory, opts: { scope: Scope; from?: number; to?: number }): unknown;
260
+ export function missedScan(memory: Memory, opts: { scope: Scope; messages: unknown[] }): unknown;
261
+
262
+ export const LAYERS: Layer[];
263
+ export const KINDS: Kind[];
264
+ export const CATEGORIES: Category[];
265
+ export const REL_TYPES: RelationType[];
266
+ export const PLAN_STATUSES: PlanStatus[];
267
+ export const DEFAULT_KNOBS: Record<string, number>;
268
+ export const KNOB_LABELS: Record<string, string>;
269
+ export const PERSONA_LIMITS: { maxStep: number; maxPerDay: number; maxDrift: number };
270
+
271
+ export function similarity(query: string, text: string): number;
272
+ export function normalizeText(text: string): string;
273
+ export function importanceOf(node: Partial<MemoryNode>, now?: number): number;
274
+ export function scopeKey(scope: string): string;
275
+
276
+ export class JsonlStore {
277
+ constructor(options: { dir: string });
278
+ dir: string;
279
+ append(scope: Scope, record: object): object;
280
+ load(scope: Scope): object[];
281
+ listScopes(): Scope[];
282
+ }
283
+
284
+ export class MemoryGraph {
285
+ constructor(options: { store: JsonlStore; options?: Record<string, unknown>; clock?: () => number });
286
+ write(scope: Scope, input: WriteOptions): WriteResult;
287
+ recall(scope: Scope, opts?: RecallOptions): RecalledNode[];
288
+ recallAcross(scopes: Scope[], opts?: RecallOptions & { perScopeLimit?: number }): RecalledNode[];
289
+ load(scope: Scope): MemoryNode[];
290
+ tick(scope: Scope, opts?: Record<string, unknown>): unknown;
291
+ tickPlans(scope: Scope, opts?: Record<string, unknown>): unknown;
292
+ setPlanStatus(scope: Scope, id: string, status: PlanStatus, reason?: string): MemoryNode;
293
+ decayRelations(scope: Scope, opts?: Record<string, unknown>): unknown;
294
+ }
295
+
296
+ export class InstantMemory {
297
+ constructor(options: { graph: MemoryGraph; options?: Record<string, unknown>; clock?: () => number });
298
+ write(scope: Scope, input: Record<string, unknown>): MemoryNode;
299
+ tick(scope: Scope, opts?: Record<string, unknown>): unknown;
300
+ revive(scope: Scope, input: Record<string, unknown>): unknown;
301
+ }
302
+
303
+ export class SharedMemory {
304
+ constructor(options: { store: JsonlStore; clock?: () => number; dailyQuota?: number });
305
+ propose(input: Record<string, unknown>): SharedEntry;
306
+ approve(id: string, opts?: { by?: string }): SharedEntry;
307
+ revoke(id: string, opts?: { by?: string; reason?: string; cascade?: boolean }): SharedEntry;
308
+ list(opts?: { status?: string }): SharedEntry[];
309
+ recall(opts?: { query?: string; limit?: number }): Array<SharedEntry & { score: number }>;
310
+ audit(opts?: { limit?: number }): unknown[];
311
+ }
312
+
313
+ export class PersonLayer {
314
+ constructor(options: { store: JsonlStore; clock?: () => number });
315
+ upsert(input: { name: string; aliases?: string[]; note?: string; confirmed?: boolean }): unknown;
316
+ resolve(name: string): unknown;
317
+ proposeMerge(a: string, b: string, opts?: { reason?: string }): unknown;
318
+ approveMerge(id: string, opts?: { by?: string }): unknown;
319
+ list(opts?: { includeProposals?: boolean }): unknown;
320
+ }
321
+
322
+ export class Persona {
323
+ constructor(options: { store: JsonlStore; clock?: () => number; limits?: Record<string, number> });
324
+ get(): Record<string, number>;
325
+ tune(key: string, delta: number, opts?: { by?: string; reason?: string }): Record<string, number>;
326
+ revert(): Record<string, number>;
327
+ summary(): string;
328
+ state(): unknown;
329
+ }
330
+
331
+ export class Compactor {
332
+ constructor(options?: CompactOptions);
333
+ compact(events: unknown[]): Promise<CompactResult>;
334
+ expand(record: CompactResult | unknown): unknown[];
335
+ stats(): unknown[];
336
+ count(events: unknown[]): number;
337
+ }
package/package.json CHANGED
@@ -1,6 +1,72 @@
1
1
  {
2
2
  "name": "dsh-memory-graph-chiyo",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.7.0",
4
+ "description": "Memory system for AI agents: anchored graph, auto-extraction, prompt assembly, conflict detection, snapshot, shared memory, consolidation. TypeScript types, zero deps.",
5
+ "type": "module",
6
+ "main": "./src/index.js",
7
+ "exports": {
8
+ ".": "./src/index.js",
9
+ "./plugin": "./plugin.js",
10
+ "./package.json": "./package.json"
11
+ },
12
+ "files": [
13
+ "src",
14
+ "bin",
15
+ "docs",
16
+ "plugin.js",
17
+ "cordis.patch.yml",
18
+ "index.d.ts",
19
+ "README.md",
20
+ "CHANGELOG.md",
21
+ "LICENSE"
22
+ ],
23
+ "keywords": [
24
+ "ai-agent",
25
+ "memory",
26
+ "knowledge-graph",
27
+ "long-term-memory",
28
+ "anchors",
29
+ "forgetting-curve",
30
+ "consolidation",
31
+ "context-compaction",
32
+ "summarization",
33
+ "context-window",
34
+ "persona",
35
+ "knowledge-graph-viz",
36
+ "rules-extraction",
37
+ "sensitive-filter",
38
+ "dsh",
39
+ "dsh-plugin",
40
+ "deepseek-harness"
41
+ ],
42
+ "license": "MIT",
43
+ "engines": {
44
+ "node": ">=20",
45
+ "dsh": ">=0.1.7-rc.1"
46
+ },
47
+ "author": "浮水梦华",
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "https://github.com/fushui-menghua/dsh-memory-graph-chiyo.git"
51
+ },
52
+ "homepage": "https://github.com/fushui-menghua/dsh-memory-graph-chiyo",
53
+ "bugs": {
54
+ "url": "https://github.com/fushui-menghua/dsh-memory-graph-chiyo/issues"
55
+ },
56
+ "bin": {
57
+ "memory-graph": "./bin/cli.mjs"
58
+ },
59
+ "types": "./index.d.ts",
60
+ "dsh": {
61
+ "bundle": {
62
+ "patch": "./cordis.patch.yml"
63
+ }
64
+ },
65
+ "scripts": {
66
+ "test": "node --test",
67
+ "demo": "node test/demo.mjs",
68
+ "cli": "node bin/cli.mjs",
69
+ "bench": "node bin/bench.mjs",
70
+ "test:plugin": "node test/plugin.test.mjs"
71
+ }
6
72
  }
package/plugin.js ADDED
@@ -0,0 +1,143 @@
1
+ /**
2
+ * plugin.js —— DSH 插件入口(把记忆库包成能装进 DSH 的插件)
3
+ *
4
+ * 同一个包两种用法:
5
+ * 1. 库:import { createMemory } from 'dsh-memory-graph-chiyo'
6
+ * 2. 插件:在 DSH profile 里列出本包,cordis 会调这里的 apply(ctx)
7
+ *
8
+ * 插件注册 5 个工具,够用又不撑爆工具表:
9
+ * memory_write 写一条记忆(带锚点)
10
+ * memory_recall 召回
11
+ * memory_health 体检
12
+ * memory_nightly 夜间整理(默认 dry-run)
13
+ * memory_shared 共享记忆(list / propose / approve / revoke)
14
+ */
15
+ import path from 'node:path';
16
+ import os from 'node:os';
17
+ import fs from 'node:fs';
18
+ import { createMemory } from './src/index.js';
19
+
20
+ // ── defineTool:宿主给了就用宿主的,没给就用本地兜底(照抄 toolkit 的思路)──
21
+ let base = (spec) => spec;
22
+ let usingHost = false;
23
+ try {
24
+ const m = await import('dsh-tools');
25
+ if (typeof m.defineTool === 'function') { base = m.defineTool; usingHost = true; }
26
+ } catch { /* 宿主没暴露就用本地兜底 */ }
27
+
28
+ export const textOutput = {
29
+ schema: { type: 'object', properties: {}, additionalProperties: true },
30
+ render: (_args, value) => [{ type: 'text', text: typeof value === 'string' ? value : JSON.stringify(value, null, 2) }],
31
+ };
32
+
33
+ export function toJsonSchema(params) {
34
+ if (!params) return { type: 'object', properties: {}, required: [] };
35
+ if (params.type === 'object') return params;
36
+ const props = {};
37
+ const required = [];
38
+ for (const [key, val] of Object.entries(params)) {
39
+ if (!val || typeof val !== 'object') continue;
40
+ const { required: isRequired, ...schema } = val;
41
+ props[key] = schema;
42
+ if (isRequired === true) required.push(key);
43
+ }
44
+ return { type: 'object', properties: props, required, additionalProperties: false };
45
+ }
46
+
47
+ function defineTool(spec) {
48
+ if (usingHost) return base({ output: textOutput, ...spec });
49
+ return base({ output: textOutput, ...spec, parameters: toJsonSchema(spec.parameters) });
50
+ }
51
+
52
+ export const name = 'memory-graph';
53
+
54
+ export function apply(ctx, config = {}) {
55
+ const home = process.env.DSH_HOME || path.join(os.homedir(), '.dsh');
56
+ const dir = path.resolve(config.dir || path.join(home, 'memory-graph'));
57
+ const defaultScope = String(config.scope || 'agent:memory');
58
+ fs.mkdirSync(dir, { recursive: true });
59
+ const mem = createMemory({ dir });
60
+ const scopeOf = (s) => String(s || defaultScope);
61
+
62
+ const tools = [
63
+ {
64
+ name: 'memory_write',
65
+ description: '写一条长期记忆。fact/plan 必须带原话锚点(anchors.quote)。同一件事重复写会自动合并。',
66
+ parameters: {
67
+ kind: { type: 'string', enum: ['fact', 'impression', 'plan'], required: true, description: '记忆类型' },
68
+ text: { type: 'string', required: true, description: '一句话陈述' },
69
+ anchors: { type: 'array', items: { type: 'object' }, description: '原话片段,如 [{quote:"我最喜欢下雨了"}]' },
70
+ category: { type: 'string', description: 'preference/experience/relationship/plan/emotion/topic' },
71
+ scope: { type: 'string', description: `记忆分区,默认 ${defaultScope}` },
72
+ },
73
+ execute: async (args = {}) => {
74
+ const r = mem.write(scopeOf(args.scope), {
75
+ nodes: [{ kind: args.kind, text: args.text, anchors: args.anchors || [], category: args.category }],
76
+ });
77
+ return { written: r.written.length, merged: r.merged.length, id: r.written[0]?.id };
78
+ },
79
+ },
80
+ {
81
+ name: 'memory_recall',
82
+ description: '按关键词/相似度召回长期记忆,返回带 id 的条目。',
83
+ parameters: {
84
+ query: { type: 'string', description: '想找什么' },
85
+ limit: { type: 'number', description: '最多几条,默认 10' },
86
+ scope: { type: 'string', description: `记忆分区,默认 ${defaultScope}` },
87
+ },
88
+ execute: async (args = {}) => {
89
+ const hits = mem.recall(scopeOf(args.scope), { query: args.query || '', limit: Number(args.limit) || 10 });
90
+ return hits.map((h) => ({ id: h.id, kind: h.kind, layer: h.layer, text: h.text, score: h.score }));
91
+ },
92
+ },
93
+ {
94
+ name: 'memory_health',
95
+ description: '记忆体检:锚点覆盖率、重复率、分层占比、陈旧度等六项。',
96
+ parameters: { scope: { type: 'string', description: `记忆分区,默认 ${defaultScope}` } },
97
+ execute: async (args = {}) => mem.healthReport({ scope: scopeOf(args.scope) }),
98
+ },
99
+ {
100
+ name: 'memory_nightly',
101
+ description: '夜间整理:推进层与即时记忆、归档噪声、强化重要记忆。默认 dry-run,apply=true 才真改。',
102
+ parameters: {
103
+ scope: { type: 'string', description: `记忆分区,默认 ${defaultScope}` },
104
+ apply: { type: 'boolean', description: 'true 才真正写入,默认只报告' },
105
+ },
106
+ execute: async (args = {}) => mem.nightly([scopeOf(args.scope)], { apply: args.apply === true }),
107
+ },
108
+ {
109
+ name: 'memory_shared',
110
+ description: '共享记忆:list 列出、propose 提名、approve 批准、revoke 撤销。只有 approved 能被其他会话召回。',
111
+ parameters: {
112
+ action: { type: 'string', enum: ['list', 'propose', 'approve', 'revoke'], required: true },
113
+ id: { type: 'string', description: '条目 id(approve/revoke 用)' },
114
+ text: { type: 'string', description: '提名内容(propose 用)' },
115
+ sourceScope: { type: 'string', description: '来源分区(propose 用)' },
116
+ by: { type: 'string', description: '操作人,默认 plugin' },
117
+ },
118
+ execute: async (args = {}) => {
119
+ const by = String(args.by || 'plugin');
120
+ if (args.action === 'list') return mem.listShared({ status: args.status || '' });
121
+ if (args.action === 'propose') return mem.proposeShared({ text: args.text, sourceScope: scopeOf(args.sourceScope), by });
122
+ if (args.action === 'approve') return mem.approveShared(args.id, { by });
123
+ if (args.action === 'revoke') return mem.revokeShared(args.id, { by });
124
+ throw new Error(`未知 action:${args.action}`);
125
+ },
126
+ },
127
+ ];
128
+
129
+ let registered = 0;
130
+ for (const spec of tools) {
131
+ try {
132
+ ctx.tools.register(defineTool(spec));
133
+ registered += 1;
134
+ } catch (e) {
135
+ // 单个工具没注册上不影响别的
136
+ try { ctx.logger?.warn?.(`[memory-graph] 工具 ${spec.name} 注册失败:${e?.message ?? e}`); } catch {}
137
+ }
138
+ }
139
+ try { ctx.logger?.info?.(`[memory-graph] 已挂载 ${registered}/${tools.length} 个工具,数据目录 ${dir},默认分区 ${defaultScope}`); } catch {}
140
+ return { dir, defaultScope, registered };
141
+ }
142
+
143
+ export default apply;