@yandy0725/memory-core 1.0.1 → 2.0.1

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.
Files changed (53) hide show
  1. package/README.md +77 -11
  2. package/README.zh.md +66 -10
  3. package/dist/config.d.ts +40 -22
  4. package/dist/config.d.ts.map +1 -1
  5. package/dist/config.js +36 -36
  6. package/dist/config.js.map +1 -1
  7. package/dist/index.d.ts +10 -9
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +5 -5
  10. package/dist/index.js.map +1 -1
  11. package/dist/injection.d.ts +9 -1
  12. package/dist/injection.d.ts.map +1 -1
  13. package/dist/injection.js +10 -3
  14. package/dist/injection.js.map +1 -1
  15. package/dist/orchestration/conversation.d.ts +1 -1
  16. package/dist/orchestration/dream.d.ts +3 -30
  17. package/dist/orchestration/dream.d.ts.map +1 -1
  18. package/dist/orchestration/dream.js +32 -57
  19. package/dist/orchestration/dream.js.map +1 -1
  20. package/dist/orchestration/extract.d.ts +8 -38
  21. package/dist/orchestration/extract.d.ts.map +1 -1
  22. package/dist/orchestration/extract.js +43 -31
  23. package/dist/orchestration/extract.js.map +1 -1
  24. package/dist/orchestration/ports.d.ts +41 -4
  25. package/dist/orchestration/ports.d.ts.map +1 -1
  26. package/dist/orchestration/status.d.ts +19 -0
  27. package/dist/orchestration/status.d.ts.map +1 -0
  28. package/dist/orchestration/status.js +23 -0
  29. package/dist/orchestration/status.js.map +1 -0
  30. package/dist/orchestration/surfacing.d.ts +9 -21
  31. package/dist/orchestration/surfacing.d.ts.map +1 -1
  32. package/dist/orchestration/surfacing.js +31 -35
  33. package/dist/orchestration/surfacing.js.map +1 -1
  34. package/dist/orchestration/task.d.ts +84 -0
  35. package/dist/orchestration/task.d.ts.map +1 -0
  36. package/dist/orchestration/task.js +145 -0
  37. package/dist/orchestration/task.js.map +1 -0
  38. package/dist/orchestration/tasks.d.ts +18 -0
  39. package/dist/orchestration/tasks.d.ts.map +1 -0
  40. package/dist/orchestration/tasks.js +16 -0
  41. package/dist/orchestration/tasks.js.map +1 -0
  42. package/package.json +3 -1
  43. package/src/config.ts +58 -46
  44. package/src/index.ts +19 -9
  45. package/src/injection.ts +18 -4
  46. package/src/orchestration/conversation.ts +1 -1
  47. package/src/orchestration/dream.ts +33 -75
  48. package/src/orchestration/extract.ts +50 -65
  49. package/src/orchestration/ports.ts +29 -4
  50. package/src/orchestration/status.ts +37 -0
  51. package/src/orchestration/surfacing.ts +45 -45
  52. package/src/orchestration/task.ts +236 -0
  53. package/src/orchestration/tasks.ts +22 -0
@@ -1,4 +1,8 @@
1
- import { buildSideQueryTask } from "../injection.js";
1
+ import { buildSideQueryTask, renderSurfacedEntries } from "../injection.js";
2
+ /** 去掉已注入的文件,得到真正进入侧查询的候选清单。 */
3
+ function candidates(input) {
4
+ return input.manifest.filter((entry) => !input.injectedFiles.has(entry.file));
5
+ }
2
6
  /** 从侧查询的回复里解析 `selected_files`,只保留确实在候选清单里的文件。 */
3
7
  function parseSelectedFiles(result, candidates, maxFiles) {
4
8
  try {
@@ -13,38 +17,30 @@ function parseSelectedFiles(result, candidates, maxFiles) {
13
17
  return [];
14
18
  }
15
19
  }
16
- /**
17
- * 跑一轮轻量侧查询,选出与用户输入相关的 entry 文件名。
18
- *
19
- * 超时/失败一律返回 `[]`,**没有回退**:auto-surfacing 是纯增益,选不出来就不注入,
20
- * 绝不因为侧查询挂了而打断用户这一轮。
21
- *
22
- * 返回的是**文件名**,不是注入内容 —— 「注入到哪个通道、本 session 已注入过哪些」是适配层的事。
23
- */
24
- export async function runSideQuery(opts, ports) {
25
- const candidates = opts.manifest.filter((entry) => !opts.injectedFiles.has(entry.file));
26
- if (candidates.length === 0)
27
- return [];
28
- const task = buildSideQueryTask(candidates, opts.userPrompt, opts.maxFiles);
29
- try {
30
- const result = await ports.headless.run({
31
- task,
32
- cwd: opts.cwd,
33
- model: opts.model,
34
- effort: opts.effort,
35
- maxTurns: 1,
36
- timeoutMs: 30_000,
37
- // 零工具就是意图:侧查询只需要回一个 JSON,给它任何工具都是白烧 token。
38
- builtinTools: "none",
39
- memoryTool: null,
40
- sessionPersistence: opts.sessionPersistence,
41
- });
42
- if (result === null)
43
- return [];
44
- return parseSelectedFiles(result, candidates, opts.maxFiles);
45
- }
46
- catch {
47
- return [];
48
- }
49
- }
20
+ /** autoSurfacing 描述符:core 自读 `cfg.autoSurfacing` 的预算,适配层只给宿主数据。 */
21
+ export const autoSurfacing = {
22
+ id: "autoSurfacing",
23
+ plan: {
24
+ maxTurns: 1,
25
+ timeoutMs: 30_000,
26
+ memoryTool: null,
27
+ lock: "none",
28
+ errorPolicy: "swallow",
29
+ },
30
+ buildPrompt(cfg, _deps, input) {
31
+ const remaining = candidates(input);
32
+ if (remaining.length === 0)
33
+ return null;
34
+ return buildSideQueryTask(remaining, input.userPrompt, cfg.autoSurfacing.maxFiles);
35
+ },
36
+ async parse(cfg, deps, input, text) {
37
+ const selected = parseSelectedFiles(text, candidates(input), cfg.autoSurfacing.maxFiles);
38
+ // 只返回真正渲染出来的文件:预算被 maxInjectionBytes 截断、或 readEntry 读不到时,
39
+ // 未渲染的文件不能进 `files`,否则适配层会把它们标记成已注入、整个 session 不再浮现。
40
+ return renderSurfacedEntries(deps.store, selected, cfg.autoSurfacing.maxEntryBytes, cfg.autoSurfacing.maxInjectionBytes);
41
+ },
42
+ notCompleted() {
43
+ return { files: [], content: "" };
44
+ },
45
+ };
50
46
  //# sourceMappingURL=surfacing.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"surfacing.js","sourceRoot":"","sources":["../../src/orchestration/surfacing.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAmBrD,kDAAkD;AAClD,SAAS,kBAAkB,CAAC,MAAc,EAAE,UAA2B,EAAE,QAAgB;IACxF,IAAI,CAAC;QACJ,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC;QAClE,IAAI,CAAC,SAAS;YAAE,OAAO,EAAE,CAAC;QAC1B,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;QACxC,MAAM,KAAK,GAAa,MAAM,CAAC,cAAc,IAAI,EAAE,CAAC;QACpD,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC7F,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,EAAE,CAAC;IACX,CAAC;AACF,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CACjC,IAAsB,EACtB,KAAsC;IAEtC,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IACxF,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACvC,MAAM,IAAI,GAAG,kBAAkB,CAAC,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC5E,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC;YACvC,IAAI;YACJ,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,QAAQ,EAAE,CAAC;YACX,SAAS,EAAE,MAAM;YACjB,2CAA2C;YAC3C,YAAY,EAAE,MAAM;YACpB,UAAU,EAAE,IAAI;YAChB,kBAAkB,EAAE,IAAI,CAAC,kBAAkB;SAC3C,CAAC,CAAC;QACH,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO,EAAE,CAAC;QAC/B,OAAO,kBAAkB,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC9D,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,EAAE,CAAC;IACX,CAAC;AACF,CAAC"}
1
+ {"version":3,"file":"surfacing.js","sourceRoot":"","sources":["../../src/orchestration/surfacing.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAkB5E,+BAA+B;AAC/B,SAAS,UAAU,CAAC,KAAqB;IACxC,OAAO,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;AAC/E,CAAC;AAED,kDAAkD;AAClD,SAAS,kBAAkB,CAAC,MAAc,EAAE,UAA2B,EAAE,QAAgB;IACxF,IAAI,CAAC;QACJ,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC;QAClE,IAAI,CAAC,SAAS;YAAE,OAAO,EAAE,CAAC;QAC1B,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;QACxC,MAAM,KAAK,GAAa,MAAM,CAAC,cAAc,IAAI,EAAE,CAAC;QACpD,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC7F,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,EAAE,CAAC;IACX,CAAC;AACF,CAAC;AAED,mEAAmE;AACnE,MAAM,CAAC,MAAM,aAAa,GAAgD;IACzE,EAAE,EAAE,eAAe;IACnB,IAAI,EAAE;QACL,QAAQ,EAAE,CAAC;QACX,SAAS,EAAE,MAAM;QACjB,UAAU,EAAE,IAAI;QAChB,IAAI,EAAE,MAAM;QACZ,WAAW,EAAE,SAAS;KACtB;IACD,WAAW,CAAC,GAAG,EAAE,KAAK,EAAE,KAAK;QAC5B,MAAM,SAAS,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;QACpC,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACxC,OAAO,kBAAkB,CAAC,SAAS,EAAE,KAAK,CAAC,UAAU,EAAE,GAAG,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;IACpF,CAAC;IACD,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI;QACjC,MAAM,QAAQ,GAAG,kBAAkB,CAAC,IAAI,EAAE,UAAU,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QACzF,0DAA0D;QAC1D,qDAAqD;QACrD,OAAO,qBAAqB,CAC3B,IAAI,CAAC,KAAK,EACV,QAAQ,EACR,GAAG,CAAC,aAAa,CAAC,aAAa,EAC/B,GAAG,CAAC,aAAa,CAAC,iBAAiB,CACnC,CAAC;IACH,CAAC;IACD,YAAY;QACX,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACnC,CAAC;CACD,CAAC"}
@@ -0,0 +1,84 @@
1
+ import { type MemoryConfig, type ModelTask, type SessionPersistenceConfig } from "../config.js";
2
+ import { type MemoryStore } from "../store/memory-store.js";
3
+ import type { MemoryAction } from "../tool/actions.js";
4
+ import type { HeadlessRunRequest, HostTranscriptPort, IncompleteOutcome, OrchestrationPorts } from "./ports.js";
5
+ /** 任务描述符里的编排骨架:全是 core 常量;两个 skip 由 `lock` 派生,不再单独编码。 */
6
+ export interface TaskPlan {
7
+ maxTurns?: number;
8
+ timeoutMs: number;
9
+ /** 注入子会话的 memory 工具 action 集;`null` = 不注入。三个任务的 builtinTools 恒为 "none"。 */
10
+ memoryTool: readonly MemoryAction[] | null;
11
+ /** 互斥与快照:none / 整轮逻辑锁 / 逻辑锁 + 进入时全目录快照。 */
12
+ lock: "none" | "logical" | "logical+snapshot";
13
+ /** 端口抛错(传输级失败)时怎么办:surfacing 吞、extract 与 dream 上抛。 */
14
+ errorPolicy: "swallow" | "rethrow";
15
+ }
16
+ /** 任务需要的 core 侧原料:store 恒有;transcript 只有 extract 用。 */
17
+ export interface TaskDeps {
18
+ store: MemoryStore;
19
+ transcript?: HostTranscriptPort;
20
+ }
21
+ /** 一次 `runTask` 调用的上下文:config 由 core 自己消费,适配层不再把它摊平成参数。 */
22
+ export interface TaskContext {
23
+ cfg: MemoryConfig;
24
+ cwd: string;
25
+ store: MemoryStore;
26
+ ports: OrchestrationPorts;
27
+ signal?: AbortSignal;
28
+ }
29
+ /** `buildRequest` 的宿主侧参数:`cfg` 的值与 plan 一起在这里合流成端口请求。 */
30
+ export interface RequestHost {
31
+ /** 子会话 cwd;三个任务恒为 memoryDir。 */
32
+ cwd: string;
33
+ /** 宿主自己的模型标识,已由 `runTask` 从 `cfg` 解析(`requiredModel`)。 */
34
+ model: string;
35
+ /** 宿主自己的思考/推理档位,core 原样透传。 */
36
+ effort?: string;
37
+ /** 宿主的会话持久化开关,core 原样透传。 */
38
+ sessionPersistence?: SessionPersistenceConfig;
39
+ signal?: AbortSignal;
40
+ }
41
+ /** `memory` 工具的写入回调;只有实现了 `memoryToolHooks` 的描述符会产出它。 */
42
+ export type MemoryToolOnWrite = (info: {
43
+ action: MemoryAction;
44
+ name?: string;
45
+ }) => void;
46
+ /** `notCompleted` 收的 `why`:core 自己的判定,或端口给的非 completed 结果。 */
47
+ export type Incomplete = {
48
+ status: "no-input";
49
+ } | {
50
+ status: "lock-busy";
51
+ } | IncompleteOutcome;
52
+ export interface MemoryTask<I, R> {
53
+ readonly id: ModelTask;
54
+ readonly plan: TaskPlan;
55
+ /** `null` = core 自己判定这一轮不跑(候选为空、消息为空)。预算值自己从 `cfg` 读。 */
56
+ buildPrompt(cfg: MemoryConfig, deps: TaskDeps, input: I): string | null;
57
+ /** 回复 → 结果。必须防御式:dsh 的 stream 通道拿不到强制结构化输出。 */
58
+ parse(cfg: MemoryConfig, deps: TaskDeps, input: I, text: string): R | Promise<R>;
59
+ /** core 没跑、或端口没跑成时怎么呈现(三个任务词汇不同的唯一合法处)。 */
60
+ notCompleted(cfg: MemoryConfig, deps: TaskDeps, input: I, why: Incomplete): R;
61
+ /** 只有 extractMemories 实现:把 `input.onWrite` 透给子会话的工具。 */
62
+ memoryToolHooks?(input: I): {
63
+ onWrite?: MemoryToolOnWrite;
64
+ } | undefined;
65
+ }
66
+ /**
67
+ * 构造端口请求:plan 的取值与 `runTask` 从 `cfg` 解析出的宿主参数在这里合流。
68
+ *
69
+ * 只收构造请求真正需要的入参,`cfg` 与 `TaskContext` 都不进来:解析留在 `runTask`
70
+ * (model / effort / sessionPersistence 都从 `cfg` 读),请求形状因此只在这里写一遍。
71
+ */
72
+ export declare function buildRequest(task: MemoryTask<unknown, unknown>, prompt: string, host: RequestHost, onWrite?: MemoryToolOnWrite): HeadlessRunRequest;
73
+ /**
74
+ * 三个 LLM 任务的通用编排。顺序是契约:
75
+ *
76
+ * 1. 读模型(缺模型在这里抛可读错误,而不是发出 `model: ""` 的请求);
77
+ * 2. 读 effort / sessionPersistence;
78
+ * 3. `buildPrompt`,`null` → `notCompleted({ status: "no-input" })`(在**任何锁之前**);
79
+ * 4. 施加 `plan.lock`(`logical` 拿不到锁 → `notCompleted({ status: "lock-busy" })`);
80
+ * 5. 端口调用(传输级异常按 `plan.errorPolicy` 收口);
81
+ * 6. `completed` → `parse`;其余 → `notCompleted(outcome)`。
82
+ */
83
+ export declare function runTask<I, R>(task: MemoryTask<I, R>, input: I, ctx: TaskContext): Promise<R>;
84
+ //# sourceMappingURL=task.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task.d.ts","sourceRoot":"","sources":["../../src/orchestration/task.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,145 @@
1
+ import { readdir } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { requiredModel, } from "../config.js";
4
+ import { BACKUP_DIR } from "../store/memory-store.js";
5
+ import { createSnapshot } from "../store/snapshot.js";
6
+ import { isReservedWindowsName } from "../store/windows-names.js";
7
+ /**
8
+ * 构造端口请求:plan 的取值与 `runTask` 从 `cfg` 解析出的宿主参数在这里合流。
9
+ *
10
+ * 只收构造请求真正需要的入参,`cfg` 与 `TaskContext` 都不进来:解析留在 `runTask`
11
+ * (model / effort / sessionPersistence 都从 `cfg` 读),请求形状因此只在这里写一遍。
12
+ */
13
+ export function buildRequest(task, prompt, host, onWrite) {
14
+ const plan = task.plan;
15
+ return {
16
+ task: prompt,
17
+ cwd: host.cwd,
18
+ model: host.model,
19
+ effort: host.effort,
20
+ maxTurns: plan.maxTurns,
21
+ timeoutMs: plan.timeoutMs,
22
+ signal: host.signal,
23
+ // 三个任务的 builtinTools 恒为 "none":侧查询只要一个 JSON,dream / extract 只走 memory 工具。
24
+ builtinTools: "none",
25
+ memoryTool: plan.memoryTool === null
26
+ ? null
27
+ : {
28
+ actions: plan.memoryTool,
29
+ skipLogicalLock: plan.lock !== "none",
30
+ skipSnapshot: plan.lock === "logical+snapshot",
31
+ onWrite,
32
+ },
33
+ // 继承规则(per-task ?? defaults)由调用方在传参前解析完,这里只做透传。
34
+ sessionPersistence: host.sessionPersistence,
35
+ };
36
+ }
37
+ /** 会话持久化的继承规则:per-task ?? `defaults`。config 消费点收在 core,适配层不再各写一份。 */
38
+ function resolveSessionPersistence(cfg, task) {
39
+ return cfg[task]?.sessionPersistence ?? cfg.defaults?.sessionPersistence;
40
+ }
41
+ /** 「宿主/库抛出来的任意值」→ 一句可读文本。core 不能 import 适配层的 errors.ts(中立性约束),
42
+ * 这里只保留 `not-run.detail` 需要的最小口径:文本、Error、普通对象 `.message`,再退到 JSON / 兜底。 */
43
+ function errorMessage(error) {
44
+ if (typeof error === "string")
45
+ return error;
46
+ if (error instanceof Error)
47
+ return error.message;
48
+ if (typeof error === "object" && error !== null) {
49
+ const message = error.message;
50
+ if (typeof message === "string")
51
+ return message;
52
+ }
53
+ try {
54
+ const json = JSON.stringify(error);
55
+ return typeof json === "string" ? json : "unknown error";
56
+ }
57
+ catch {
58
+ return "unknown error";
59
+ }
60
+ }
61
+ /** 进入时快照要拍的目录条目:跳过 dotfile;win32 上再跳过保留设备名(CON 等)。 */
62
+ async function snapshotFiles(memoryDir, platform = process.platform) {
63
+ const entries = await readdir(memoryDir, { withFileTypes: true }).catch(() => []);
64
+ return (entries
65
+ .filter((entry) => entry.isFile() && !entry.name.startsWith("."))
66
+ // win32:保留设备名文件按名打开会命中设备(CON 等),`createSnapshot` 的 cp 会失败;
67
+ // dream 是 fail-closed 的,一次 cp 失败就打断整轮(与 `MemoryStore` 的 `#entryFiles` 同一契约)。
68
+ .filter((entry) => !(platform === "win32" && isReservedWindowsName(entry.name)))
69
+ .map((entry) => entry.name)
70
+ .sort());
71
+ }
72
+ /**
73
+ * 三个 LLM 任务的通用编排。顺序是契约:
74
+ *
75
+ * 1. 读模型(缺模型在这里抛可读错误,而不是发出 `model: ""` 的请求);
76
+ * 2. 读 effort / sessionPersistence;
77
+ * 3. `buildPrompt`,`null` → `notCompleted({ status: "no-input" })`(在**任何锁之前**);
78
+ * 4. 施加 `plan.lock`(`logical` 拿不到锁 → `notCompleted({ status: "lock-busy" })`);
79
+ * 5. 端口调用(传输级异常按 `plan.errorPolicy` 收口);
80
+ * 6. `completed` → `parse`;其余 → `notCompleted(outcome)`。
81
+ */
82
+ export async function runTask(task, input, ctx) {
83
+ const cfg = ctx.cfg;
84
+ const deps = { store: ctx.store, transcript: ctx.ports.transcript };
85
+ const model = requiredModel(cfg, task.id);
86
+ const effort = cfg[task.id]?.thinkLevel;
87
+ const sessionPersistence = resolveSessionPersistence(cfg, task.id);
88
+ const prompt = task.buildPrompt(cfg, deps, input);
89
+ if (prompt === null)
90
+ return task.notCompleted(cfg, deps, input, { status: "no-input" });
91
+ const run = async () => {
92
+ const request = buildRequest(task, prompt, { cwd: ctx.cwd, model, effort, sessionPersistence, signal: ctx.signal }, task.memoryToolHooks?.(input)?.onWrite);
93
+ let outcome;
94
+ try {
95
+ outcome = await ctx.ports.headless.run(request);
96
+ }
97
+ catch (e) {
98
+ if (task.plan.errorPolicy === "swallow") {
99
+ return task.notCompleted(cfg, deps, input, {
100
+ status: "not-run",
101
+ cause: "host-refused",
102
+ detail: errorMessage(e),
103
+ });
104
+ }
105
+ throw e;
106
+ }
107
+ if (outcome.status === "completed")
108
+ return await task.parse(cfg, deps, input, outcome.text);
109
+ return task.notCompleted(cfg, deps, input, outcome);
110
+ };
111
+ switch (task.plan.lock) {
112
+ case "none":
113
+ return run();
114
+ case "logical": {
115
+ const result = await ctx.store.tryWithLogicalLock(run);
116
+ if (result === null)
117
+ return task.notCompleted(cfg, deps, input, { status: "lock-busy" });
118
+ return result;
119
+ }
120
+ case "logical+snapshot": {
121
+ // 只有 dream 用这档锁。整轮持锁是刻意的:dream 的多次 `replace` / `remove` 必须对
122
+ // 同进程的 `memory add` 与 extract 呈现为**一个**原子区间,否则用户会在 dream 中途读到
123
+ // 半合并的状态。锁在进程内(不是 `.lock`),所以跨进程的其它 worktree 仍能正常写入 ——
124
+ // 它们的每次物理写入只被挡毫秒级。
125
+ //
126
+ // 其内部的每个原语调用必须带 `{ skipLogicalLock: true, skipSnapshot: true }`(由
127
+ // `memoryTool` 请求字段表达,适配层据此构造子会话的工具),否则它们会去抢自己已持有的
128
+ // 锁而自锁到超时,并且会为同一批变更各拍一次快照。
129
+ return ctx.store.withLogicalLock(async () => {
130
+ // 启动自检:忘了包 withLogicalLock 会静默失去整轮互斥。
131
+ if (!ctx.store.logicalLockActive()) {
132
+ throw new Error(`${task.id} must run under the memory logical lock`);
133
+ }
134
+ // 进入时对整目录拍一次快照;这一轮里各原语的逐文件快照被 skipSnapshot 跳过。
135
+ const files = await snapshotFiles(ctx.cwd, ctx.store.cfg.platform);
136
+ await createSnapshot(join(ctx.cwd, BACKUP_DIR), task.id, files, ctx.cwd, {
137
+ keep: ctx.store.cfg.lock.snapshotKeep,
138
+ platform: ctx.store.cfg.platform,
139
+ });
140
+ return run();
141
+ });
142
+ }
143
+ }
144
+ }
145
+ //# sourceMappingURL=task.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task.js","sourceRoot":"","sources":["../../src/orchestration/task.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import type { MemoryTask } from "./task.js";
2
+ /**
3
+ * 任务注册表:描述符的唯一声明处。
4
+ *
5
+ * 这一层单独成文件是为了让依赖方向单向:`tasks.ts → 描述符 → task.ts`。注册表原本住在 `task.ts`
6
+ * 里,而描述符要从 `task.ts` 拿 `runTask` / `MemoryTask`,于是构成模块环 —— 当时只靠「没人
7
+ * 在模块求值时读 `runTask`」才没炸(TDZ 侥幸)。把注册表挪出来后环消失,描述符里出现顶层
8
+ * 使用也不会在 import 时崩。
9
+ *
10
+ * `satisfies Record<ModelTask, MemoryTask<any, any>>` 让注册表**可证明完整**:漏一个描述符、
11
+ * 或键名写错,这里直接编译报错,而不是等调用方在运行时撞 `TASKS.dream === undefined`。
12
+ */
13
+ export declare const TASKS: {
14
+ readonly autoSurfacing: MemoryTask<import("./surfacing.js").SurfacingInput, import("./surfacing.js").SurfacingResult>;
15
+ readonly extractMemories: MemoryTask<import("./extract.js").ExtractInput, import("./extract.js").ExtractResult>;
16
+ readonly dream: MemoryTask<Record<string, never>, string>;
17
+ };
18
+ //# sourceMappingURL=tasks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tasks.d.ts","sourceRoot":"","sources":["../../src/orchestration/tasks.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE5C;;;;;;;;;;GAUG;AACH,eAAO,MAAM,KAAK;;;;CAIjB,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { dream } from "./dream.js";
2
+ import { extractMemories } from "./extract.js";
3
+ import { autoSurfacing } from "./surfacing.js";
4
+ /**
5
+ * 任务注册表:描述符的唯一声明处。
6
+ *
7
+ * 这一层单独成文件是为了让依赖方向单向:`tasks.ts → 描述符 → task.ts`。注册表原本住在 `task.ts`
8
+ * 里,而描述符要从 `task.ts` 拿 `runTask` / `MemoryTask`,于是构成模块环 —— 当时只靠「没人
9
+ * 在模块求值时读 `runTask`」才没炸(TDZ 侥幸)。把注册表挪出来后环消失,描述符里出现顶层
10
+ * 使用也不会在 import 时崩。
11
+ *
12
+ * `satisfies Record<ModelTask, MemoryTask<any, any>>` 让注册表**可证明完整**:漏一个描述符、
13
+ * 或键名写错,这里直接编译报错,而不是等调用方在运行时撞 `TASKS.dream === undefined`。
14
+ */
15
+ export const TASKS = { autoSurfacing, extractMemories, dream };
16
+ //# sourceMappingURL=tasks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tasks.js","sourceRoot":"","sources":["../../src/orchestration/tasks.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAG/C;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,EAAE,aAAa,EAAE,eAAe,EAAE,KAAK,EAI3D,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yandy0725/memory-core",
3
- "version": "1.0.1",
3
+ "version": "2.0.1",
4
4
  "description": "Harness-agnostic file-system memory layer: one memory = one Markdown file, a MEMORY.md index, two-level locking, snapshots, and the LLM orchestration around them.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -28,6 +28,8 @@
28
28
  "scripts": {
29
29
  "build": "tsc -p tsconfig.build.json",
30
30
  "prepublishOnly": "npm run build",
31
+ "prepack": "node ../scripts/sync-license.mjs",
32
+ "postpack": "node ../scripts/sync-license.mjs --remove",
31
33
  "test": "vitest run",
32
34
  "test:watch": "vitest",
33
35
  "typecheck": "tsc --noEmit",
package/src/config.ts CHANGED
@@ -1,8 +1,15 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
1
  import { homedir } from "node:os";
3
2
  import { isAbsolute, join } from "node:path";
4
3
 
5
- export type ThinkLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
4
+ /**
5
+ * 宿主的思考/推理档位,**不透明字符串**:core 原样透传,不解释、不校验。
6
+ * pi 的合法值是 `off|minimal|low|medium|high|xhigh`;dsh 的合法值按 provider 路线是数据
7
+ * (DeepSeek 路线 `off|low|high|max`),所以 core 不能把它写成闭枚举。
8
+ * 合法性由适配层在启动期用宿主的 resolve 接口校验。
9
+ */
10
+ export type EffortLevel = string;
11
+ /** @deprecated pi 词汇,等价于 `EffortLevel`;保留是为了已发布适配层的类型重导出不破。 */
12
+ export type ThinkLevel = EffortLevel;
6
13
 
7
14
  /** Session persistence configuration for headless memory-agent sub-sessions. */
8
15
  export interface SessionPersistenceConfig {
@@ -22,7 +29,7 @@ export interface DefaultsConfig {
22
29
  export interface AutoSurfacingConfig {
23
30
  enabled: boolean;
24
31
  model?: string;
25
- thinkLevel: ThinkLevel;
32
+ thinkLevel: EffortLevel;
26
33
  maxFiles: number;
27
34
  /**
28
35
  * 单条 entry 注入正文的字节上限。
@@ -37,7 +44,7 @@ export interface ExtractMemoriesConfig {
37
44
  /** 每轮自动提取。opt-in:默认 `false` —— 开启后每轮结束都会跑一次 headless 模型调用。 */
38
45
  enabled: boolean;
39
46
  model?: string;
40
- thinkLevel: ThinkLevel;
47
+ thinkLevel: EffortLevel;
41
48
  maxContextTokens: number;
42
49
  /** 渲染进 extract prompt 时,单条 tool_result 的字符上限。 */
43
50
  maxToolResultChars: number;
@@ -68,11 +75,14 @@ export interface MemoryConfig {
68
75
  * **没有** ttl / 心跳 / 接管字段:跨进程 `.lock` 永远只持毫秒且永不自动回收。
69
76
  */
70
77
  lock: { timeoutMs: number; snapshotKeep: number };
78
+ /**
79
+ * dream 任务本身:模型、思考档位、会话落盘。
80
+ * 触发它的**策略**(隔多少个会话 / 多少小时后提醒)是宿主知识,**不**在这里 —— 需要它的适配层
81
+ * 自己持有(pi 的 `PiHostConfig`),dsh 没有这套策略。
82
+ */
71
83
  dream: {
72
- nudgeAfterSessions: number;
73
- nudgeAfterHours: number;
74
84
  model?: string;
75
- thinkLevel: ThinkLevel;
85
+ thinkLevel: EffortLevel;
76
86
  sessionPersistence?: SessionPersistenceConfig;
77
87
  };
78
88
  sessionSearch: { maxSessions: number; maxMatches: number };
@@ -96,7 +106,7 @@ export const DEFAULT_CONFIG: MemoryConfig = {
96
106
  memIndexInjectMaxLines: 50,
97
107
  memIndexInjectMaxBytes: 16384,
98
108
  lock: { timeoutMs: 5000, snapshotKeep: 5 },
99
- dream: { nudgeAfterSessions: 5, nudgeAfterHours: 24, thinkLevel: "high" },
109
+ dream: { thinkLevel: "high" },
100
110
  sessionSearch: { maxSessions: 10, maxMatches: 5 },
101
111
  autoSurfacing: {
102
112
  enabled: true,
@@ -125,13 +135,32 @@ export function taskModel(cfg: MemoryConfig, task: ModelTask): string | undefine
125
135
  return cfg[task]?.model ?? cfg.defaults?.model;
126
136
  }
127
137
 
128
- /** 会执行的任务及其模型值。dream 恒在执行集合内(没有包级开关可以让它不跑)。 */
129
- export function requiredModels(cfg: MemoryConfig): Array<{ task: ModelTask; value: string | undefined }> {
130
- const out: Array<{ task: ModelTask; value: string | undefined }> = [
131
- { task: "dream", value: taskModel(cfg, "dream") },
132
- ];
133
- if (cfg.extractMemories.enabled) out.push({ task: "extractMemories", value: taskModel(cfg, "extractMemories") });
134
- if (cfg.autoSurfacing.enabled) out.push({ task: "autoSurfacing", value: taskModel(cfg, "autoSurfacing") });
138
+ /**
139
+ * 会执行的任务及其模型值。
140
+ *
141
+ * `tasks`:适配层声明自己实现了哪些任务(如 dsh 本轮不实现 dream,就传 `["autoSurfacing"]`)。
142
+ * 省略 = 既有行为不变(dream 恒在执行集合内,另两个按 `enabled`);给定则**只**考虑这些任务,
143
+ * dream 不再被强制加入,另两个仍按 `enabled` 过滤。顺序与调用方列举顺序无关,恒为
144
+ * dream → extractMemories → autoSurfacing。
145
+ *
146
+ * 缺省路径上**不是**逐字不变,唯一的例外(实现期已裁定):`deepMerge` 会把用户写的
147
+ * `"extractMemories": null` / `"autoSurfacing": null` 原样带进来,旧实现直接读 `.enabled` 会抛
148
+ * `TypeError`,现在按 falsy 跳过该任务。这与 `taskModel` 对 `"dream": null` 的既有防御同一取向:
149
+ * 配置读不懂时应给出「该任务关闭 / 没有模型」这种可读结果,而不是把启动校验变成一句看不懂的
150
+ * 初始化失败。
151
+ */
152
+ export function requiredModels(
153
+ cfg: MemoryConfig,
154
+ tasks?: readonly ModelTask[],
155
+ ): Array<{ task: ModelTask; value: string | undefined }> {
156
+ const order: readonly ModelTask[] = ["dream", "extractMemories", "autoSurfacing"];
157
+ const out: Array<{ task: ModelTask; value: string | undefined }> = [];
158
+ for (const task of order) {
159
+ if (tasks !== undefined && !tasks.includes(task)) continue;
160
+ // dream 没有包级开关:进了候选即在执行集合内(省略 `tasks` 时它恒是候选)。另两个看 `enabled`。
161
+ // 这里用 `?.`,与 `taskModel` 同理:`deepMerge` 会把用户写的 `"extractMemories": null` 原样带进来。
162
+ if (task === "dream" || cfg[task]?.enabled) out.push({ task, value: taskModel(cfg, task) });
163
+ }
135
164
  return out;
136
165
  }
137
166
 
@@ -139,10 +168,15 @@ export function requiredModels(cfg: MemoryConfig): Array<{ task: ModelTask; valu
139
168
  * 启动校验:空数组 = 通过。
140
169
  * `resolve` 由调用方注入(生产时是 `(v) => resolveModel(v, ctx.modelRegistry) !== undefined`),
141
170
  * 因此本函数不依赖 SDK 的 registry 类型,可用假 resolve 单测。
171
+ * `tasks`:原样透传给 `requiredModels`(只校验列出的任务;`null` 子树跳过那个例外同上)。
142
172
  */
143
- export function modelConfigErrors(cfg: MemoryConfig, resolve: (value: string) => boolean): string[] {
173
+ export function modelConfigErrors(
174
+ cfg: MemoryConfig,
175
+ resolve: (value: string) => boolean,
176
+ tasks?: readonly ModelTask[],
177
+ ): string[] {
144
178
  const errors: string[] = [];
145
- for (const { task, value } of requiredModels(cfg)) {
179
+ for (const { task, value } of requiredModels(cfg, tasks)) {
146
180
  if (value === undefined) {
147
181
  errors.push(`no model for ${task} — set "${task}.model" or "defaults.model" in memory.json`);
148
182
  } else if (!resolve(value)) {
@@ -188,13 +222,13 @@ function deepMerge<T>(base: T, over: Partial<T>): T {
188
222
  return out;
189
223
  }
190
224
 
191
- function readJsonSafe(path: string): Partial<MemoryConfig> {
192
- try {
193
- if (existsSync(path)) return JSON.parse(readFileSync(path, "utf-8")) as Partial<MemoryConfig>;
194
- } catch {
195
- // ignore malformed
196
- }
197
- return {};
225
+ /**
226
+ * 把 `overlay` 深合并到 `base` 上:`undefined` 跳过、plain object 递归、数组整体替换、`null` 子树原样带入。
227
+ * 返回**新对象**,不改 `base` —— 这是合并语义的**唯一**一份实现:适配层读文件、读行 config、
228
+ * 读任何宿主侧的 overlay,都把它交给这里。core 不读任何配置文件。
229
+ */
230
+ export function mergeConfig(base: MemoryConfig, overlay: unknown): MemoryConfig {
231
+ return deepMerge(base, (overlay ?? {}) as Partial<MemoryConfig>);
198
232
  }
199
233
 
200
234
  /**
@@ -211,25 +245,3 @@ export function defaultMemoryRoot(env: NodeJS.ProcessEnv = process.env, home: st
211
245
  const base = xdg && isAbsolute(xdg) ? xdg : join(home, ".local", "share");
212
246
  return join(base, "simple-memory");
213
247
  }
214
-
215
- export interface LoadConfigFromOptions {
216
- /** 命名/展开规则跟随的平台。默认 `process.platform`。 */
217
- platform?: NodeJS.Platform;
218
- /** 覆盖共享数据根(测试注入缝)。默认 `defaultMemoryRoot()`。 */
219
- memoryRoot?: string;
220
- }
221
-
222
- /**
223
- * 把 `files` 里存在且可解析的 JSON 按顺序 deep-merge 到默认值上,最后展开 `memoryDir` 的 `~`。
224
- *
225
- * core **不认识任何宿主的配置文件路径**:`files` 由适配层组装(全局配置在哪、项目配置在哪、
226
- * 项目不受信任时该不该读,全都是宿主的事)。不存在与 JSON 损坏的文件都跳过。
227
- */
228
- export async function loadConfigFrom(files: string[], opts: LoadConfigFromOptions = {}): Promise<MemoryConfig> {
229
- let cfg: MemoryConfig = { ...DEFAULT_CONFIG, memoryDir: opts.memoryRoot ?? defaultMemoryRoot() };
230
- for (const file of files) {
231
- cfg = deepMerge(cfg, readJsonSafe(file));
232
- }
233
- cfg.memoryDir = expandTilde(cfg.memoryDir, opts.platform ?? process.platform);
234
- return cfg;
235
- }
package/src/index.ts CHANGED
@@ -52,12 +52,12 @@ export { sanitizeForInjection, stripInvisibleChars } from "./store/sanitize.js";
52
52
  export { escapeWindowsTrailing, isReservedWindowsName, windowsSafeName } from "./store/windows-names.js";
53
53
 
54
54
  // ── 配置(默认值、合并语义、模型校验、共享数据根)──────────────────────────────
55
- // 配置文件**在哪**是宿主知识,由适配层组装成 `loadConfigFrom` 的 `files` 入参。
55
+ // core 只给默认值与合并语义;配置文件在哪、读不读,是适配层的事。
56
56
  export {
57
57
  DEFAULT_CONFIG,
58
58
  defaultMemoryRoot,
59
59
  expandTilde,
60
- loadConfigFrom,
60
+ mergeConfig,
61
61
  modelConfigErrors,
62
62
  requiredModel,
63
63
  requiredModels,
@@ -66,8 +66,8 @@ export {
66
66
  export type {
67
67
  AutoSurfacingConfig,
68
68
  DefaultsConfig,
69
+ EffortLevel,
69
70
  ExtractMemoriesConfig,
70
- LoadConfigFromOptions,
71
71
  MemoryConfig,
72
72
  ModelTask,
73
73
  SessionPersistenceConfig,
@@ -102,20 +102,30 @@ export type { EntryManifest } from "./injection.js";
102
102
  // ── orchestration/ LLM 编排 ───────────────────────────────────────────────────
103
103
  // 适配层实现 `HeadlessAgentPort` 与 `HostTranscriptPort` 两个端口,就能拿到与 pi 完全相同的
104
104
  // dream / extract / 侧查询行为(prompt 文本、锁与快照边界、截断预算、错误语义)。
105
- export { runDream } from "./orchestration/dream.js";
106
- export type { RunDreamOpts } from "./orchestration/dream.js";
107
- export { runExtract } from "./orchestration/extract.js";
108
- export type { ExtractPorts, RunExtractOpts } from "./orchestration/extract.js";
105
+ export { runTask } from "./orchestration/task.js";
106
+ export { TASKS } from "./orchestration/tasks.js";
107
+ export { moduleStatusRows } from "./orchestration/status.js";
108
+ export type { ModuleStatusRow } from "./orchestration/status.js";
109
+ export type {
110
+ Incomplete,
111
+ MemoryTask,
112
+ MemoryToolOnWrite,
113
+ TaskContext,
114
+ TaskDeps,
115
+ TaskPlan,
116
+ } from "./orchestration/task.js";
117
+ export type { ExtractInput, ExtractResult } from "./orchestration/extract.js";
109
118
  export { renderConversation } from "./orchestration/conversation.js";
110
119
  export type { ConversationLimits } from "./orchestration/conversation.js";
111
- export { runSideQuery } from "./orchestration/surfacing.js";
112
- export type { RunSideQueryOpts } from "./orchestration/surfacing.js";
120
+ export type { SurfacingInput, SurfacingResult } from "./orchestration/surfacing.js";
113
121
  export { buildDreamTask, buildExtractTask } from "./orchestration/prompts.js";
114
122
  export type {
115
123
  ExtractMessage,
116
124
  HeadlessAgentPort,
125
+ HeadlessOutcome,
117
126
  HeadlessRunRequest,
118
127
  HostTranscriptPort,
128
+ IncompleteOutcome,
119
129
  OrchestrationPorts,
120
130
  } from "./orchestration/ports.js";
121
131
  export { memoryToolSchema } from "./tool/schema.js";