@coralai/sps-plugin-api 0.10.0 → 0.11.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/dist/index.d.ts +124 -4
- package/dist/index.js +44 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -242,13 +242,133 @@ export interface SpsRegistryByFactory<T = unknown> {
|
|
|
242
242
|
register(factory: T): () => void;
|
|
243
243
|
list(): string[];
|
|
244
244
|
}
|
|
245
|
+
/** 作用域:global 跨项目 / project 单项目 / agent(harness 会话)。 */
|
|
246
|
+
export type MemoryScope = 'global' | 'project' | 'agent';
|
|
247
|
+
export type MemoryCategory = 'convention' | 'decision' | 'gotcha' | 'pattern' | 'reference' | 'note';
|
|
248
|
+
/** 出处:agent 主动写 / capture 自动捕获 / human 人(CLI/Console)。 */
|
|
249
|
+
export type MemorySource = 'agent' | 'capture' | 'human';
|
|
250
|
+
/** 定位一个作用域。`project` scope 必带 project;`agent` scope 必带 agentId。 */
|
|
251
|
+
export interface MemoryScopeRef {
|
|
252
|
+
scope: MemoryScope;
|
|
253
|
+
project?: string;
|
|
254
|
+
agentId?: string;
|
|
255
|
+
}
|
|
256
|
+
export interface MemoryEntry {
|
|
257
|
+
/** 后端内部的裸 id(不带 `<backend>:` 前缀)。 */
|
|
258
|
+
id: string;
|
|
259
|
+
title: string;
|
|
260
|
+
scope: MemoryScope;
|
|
261
|
+
category: MemoryCategory;
|
|
262
|
+
tags: string[];
|
|
263
|
+
/** 显著度 1..5。 */
|
|
264
|
+
salience: number;
|
|
265
|
+
/** 被读回全文的次数 —— "真被用到"的硬信号。后端不支持时恒 0 即可。 */
|
|
266
|
+
uses: number;
|
|
267
|
+
source: MemorySource;
|
|
268
|
+
project?: string;
|
|
269
|
+
created: string;
|
|
270
|
+
updated: string;
|
|
271
|
+
body: string;
|
|
272
|
+
}
|
|
273
|
+
/** 不带 id = 新建;带 id = 更新。 */
|
|
274
|
+
export interface MemorySaveInput {
|
|
275
|
+
id?: string;
|
|
276
|
+
title: string;
|
|
277
|
+
body: string;
|
|
278
|
+
category?: MemoryCategory;
|
|
279
|
+
tags?: string[];
|
|
280
|
+
salience?: number;
|
|
281
|
+
source?: MemorySource;
|
|
282
|
+
project?: string;
|
|
283
|
+
}
|
|
284
|
+
export interface MemoryRecallHit {
|
|
285
|
+
/** **必须带 `<backend>:` 前缀** —— 调用方据此知道回哪个后端读全文。 */
|
|
286
|
+
id: string;
|
|
287
|
+
/** 产出这条结果的后端 id。 */
|
|
288
|
+
source: string;
|
|
289
|
+
title: string;
|
|
290
|
+
snippet: string;
|
|
291
|
+
score: number;
|
|
292
|
+
scope?: string;
|
|
293
|
+
project?: string;
|
|
294
|
+
category?: string;
|
|
295
|
+
tags?: string[];
|
|
296
|
+
}
|
|
297
|
+
export interface MemoryRecallOpts {
|
|
298
|
+
tags?: string[];
|
|
299
|
+
limit?: number;
|
|
300
|
+
}
|
|
245
301
|
/**
|
|
246
|
-
*
|
|
247
|
-
*
|
|
302
|
+
* 记忆后端。**必须实现 6 个,可选 4 个**。
|
|
303
|
+
*
|
|
304
|
+
* ⚠️ 可选的四个宿主都有默认实现 —— 不实现不是缺陷:
|
|
305
|
+
* 没有排序反馈机制的后端不该被迫写 `bumpUses`,没有派生产物的不该写 `reindex`。
|
|
306
|
+
*
|
|
307
|
+
* 🔴 读与写**必须一起实现**。只做一半的症状是"存进去的和读出来的不是一回事",
|
|
308
|
+
* 比彻底不工作难查得多。
|
|
248
309
|
*/
|
|
249
310
|
export interface SpsMemory {
|
|
250
|
-
|
|
251
|
-
|
|
311
|
+
recall(refs: MemoryScopeRef[], query: string, opts?: MemoryRecallOpts): Promise<MemoryRecallHit[]>;
|
|
312
|
+
listEntries(ref: MemoryScopeRef): MemoryEntry[];
|
|
313
|
+
readEntry(ref: MemoryScopeRef, id: string): MemoryEntry | null;
|
|
314
|
+
listAllScopes(): MemoryScopeRef[];
|
|
315
|
+
saveEntry(ref: MemoryScopeRef, input: MemorySaveInput): MemoryEntry;
|
|
316
|
+
deleteEntry(ref: MemoryScopeRef, id: string): boolean;
|
|
317
|
+
/** 后端当前是否可用。返回 false **不是错误** —— 调用方据此静默跳过。 */
|
|
318
|
+
enabled(): boolean;
|
|
319
|
+
bumpUses(ref: MemoryScopeRef, id: string): void;
|
|
320
|
+
/** 重建该作用域的派生产物。批量写入后调一次,不是每条都调。 */
|
|
321
|
+
reindex(ref: MemoryScopeRef): void;
|
|
322
|
+
promoteEntry(from: MemoryScopeRef, id: string, to: MemoryScopeRef, opts: {
|
|
323
|
+
move?: boolean;
|
|
324
|
+
}): MemoryEntry | null;
|
|
325
|
+
/**
|
|
326
|
+
* 宿主写完记忆配置后调一次,让长命进程无需重启即读到新值。
|
|
327
|
+
* 后端不缓存配置 ⇒ 空实现即可。
|
|
328
|
+
*/
|
|
329
|
+
reloadConfig(): void;
|
|
330
|
+
/** 后端支持哪些维护操作。默认两个都不支持。 */
|
|
331
|
+
readonly supports: {
|
|
332
|
+
gc?: boolean;
|
|
333
|
+
migrate?: boolean;
|
|
334
|
+
};
|
|
335
|
+
/** 回收一个作用域(硬删 tombstone、去重)。`supports.gc` 为假时不会被调用。 */
|
|
336
|
+
gc(ref: MemoryScopeRef): Promise<MemoryGcStats>;
|
|
337
|
+
/**
|
|
338
|
+
* 迁移该后端自己的历史数据结构。`supports.migrate` 为假时不会被调用。
|
|
339
|
+
*
|
|
340
|
+
* ⚠️ 这是**后端的历史包袱**,不是通用能力 —— 一个新后端不该有可迁的旧结构。
|
|
341
|
+
*/
|
|
342
|
+
migrate(target: {
|
|
343
|
+
project?: string;
|
|
344
|
+
all?: boolean;
|
|
345
|
+
}): MemoryMigrateStats;
|
|
346
|
+
}
|
|
347
|
+
export interface MemoryGcStats {
|
|
348
|
+
/** 硬删的 tombstone 数。 */
|
|
349
|
+
purged: number;
|
|
350
|
+
/** 去重软删的重复条数。 */
|
|
351
|
+
deduped: number;
|
|
352
|
+
/** 收敛后剩余条数。 */
|
|
353
|
+
kept: number;
|
|
354
|
+
}
|
|
355
|
+
/** 迁移了多少条 —— 键由后端自定(本地实现给 global/agents/projects)。 */
|
|
356
|
+
export type MemoryMigrateStats = Record<string, number>;
|
|
357
|
+
/** 写入被脱敏规则丢弃时,后端抛出的错误应带这个 code。宿主据此与真错误区分。 */
|
|
358
|
+
export declare const MEMORY_REDACTED_CODE = "memory-redacted";
|
|
359
|
+
/** 去重归一化:小写、去 markdown 标点、压空白。**这是"什么算重复"的唯一判据**。 */
|
|
360
|
+
export declare function normalizeMemoryBody(s: string): string;
|
|
361
|
+
export interface MemoryDigestOpts {
|
|
362
|
+
/** 标题清单最多列几条(默认 60)。 */
|
|
363
|
+
maxTitles?: number;
|
|
364
|
+
/** 内联几条高 salience 正文(默认 6)。 */
|
|
365
|
+
maxBodies?: number;
|
|
366
|
+
/** 每条正文最多字符(默认 500)。 */
|
|
367
|
+
bodyChars?: number;
|
|
368
|
+
}
|
|
369
|
+
/** entries → 摘要 markdown(标题清单 + top 高 salience 正文)。空集合返回空串。 */
|
|
370
|
+
export declare function buildMemoryDigest(entries: MemoryEntry[], opts?: MemoryDigestOpts): string;
|
|
371
|
+
/** ⚠️ 甲·工厂型(换实现,不是收一组);今天没有第三方在换它。 */
|
|
252
372
|
export interface SpsNotifier {
|
|
253
373
|
open(config: unknown): {
|
|
254
374
|
send(message: string, level?: 'info' | 'success' | 'warning' | 'error'): Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,50 @@
|
|
|
11
11
|
* ⚠️ 这里只声明**插件会调的那一面**。服务内部还有别的方法(比如注册表的
|
|
12
12
|
* `list()`),不写进来 —— 写进来就等于承诺它们不变,而那不是我们打算承诺的。
|
|
13
13
|
*/
|
|
14
|
+
// ── 跨后端必须一致的算法 ──────────────────────────────────────────────────
|
|
15
|
+
//
|
|
16
|
+
// 🔴 放这里的判据只有一条:**两个后端对它给出不同答案会出错**。
|
|
17
|
+
// "什么算重复"若两个后端不一致,同一条记忆在不同后端行为不同;
|
|
18
|
+
// 摘要格式不一致,人看到的 MEMORY.md 会跟着换后端变形。
|
|
19
|
+
// ⚠️ 别把"本地实现顺手用得上的工具"塞进来 —— 那属于后端自己。
|
|
20
|
+
/** 写入被脱敏规则丢弃时,后端抛出的错误应带这个 code。宿主据此与真错误区分。 */
|
|
21
|
+
export const MEMORY_REDACTED_CODE = 'memory-redacted';
|
|
22
|
+
/** 去重归一化:小写、去 markdown 标点、压空白。**这是"什么算重复"的唯一判据**。 */
|
|
23
|
+
export function normalizeMemoryBody(s) {
|
|
24
|
+
return s
|
|
25
|
+
.toLowerCase()
|
|
26
|
+
.replace(/[#*`_>\-\s]+/g, ' ')
|
|
27
|
+
.replace(/[^\p{L}\p{N} ]+/gu, '')
|
|
28
|
+
.trim();
|
|
29
|
+
}
|
|
30
|
+
/** entries → 摘要 markdown(标题清单 + top 高 salience 正文)。空集合返回空串。 */
|
|
31
|
+
export function buildMemoryDigest(entries, opts = {}) {
|
|
32
|
+
const maxTitles = opts.maxTitles ?? 60;
|
|
33
|
+
const maxBodies = opts.maxBodies ?? 6;
|
|
34
|
+
const bodyChars = opts.bodyChars ?? 500;
|
|
35
|
+
if (entries.length === 0)
|
|
36
|
+
return '';
|
|
37
|
+
const ranked = entries
|
|
38
|
+
.slice()
|
|
39
|
+
.sort((a, b) => b.salience - a.salience || b.updated.localeCompare(a.updated));
|
|
40
|
+
const lines = ['# 记忆摘要', '', '## 索引'];
|
|
41
|
+
for (const e of ranked.slice(0, maxTitles)) {
|
|
42
|
+
const tags = e.tags.length ? ` #${e.tags.join(' #')}` : '';
|
|
43
|
+
lines.push(`- [${e.id}] (${e.category}, s${e.salience}) ${e.title}${tags}`);
|
|
44
|
+
}
|
|
45
|
+
if (ranked.length > maxTitles) {
|
|
46
|
+
lines.push(`- …还有 ${ranked.length - maxTitles} 条(用 memory_recall 检索)`);
|
|
47
|
+
}
|
|
48
|
+
const bodies = ranked.slice(0, maxBodies);
|
|
49
|
+
if (bodies.length) {
|
|
50
|
+
lines.push('', '## 高显著度');
|
|
51
|
+
for (const e of bodies) {
|
|
52
|
+
const body = e.body.length > bodyChars ? `${e.body.slice(0, bodyChars)}…` : e.body;
|
|
53
|
+
lines.push('', `### ${e.title}`, body);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return lines.join('\n').trim();
|
|
57
|
+
}
|
|
14
58
|
// ── 服务名 ───────────────────────────────────────────────────────────────
|
|
15
59
|
/** 插件 `inject` 里写的名字。 */
|
|
16
60
|
export const SPS_SERVICES = {
|