@qltk/pi-mnemo 0.1.0 → 0.2.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/CHANGELOG.md +26 -0
- package/README.md +14 -1
- package/package.json +1 -1
- package/src/config.ts +71 -35
- package/src/orchestrator.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.0 — 2026-09-05
|
|
4
|
+
|
|
5
|
+
配置文件 seam:`~/.pi/agent/mnemo.json`(或 `$PI_MNEMO_CONFIG`),优先级 文件 > env > 默认。
|
|
6
|
+
|
|
7
|
+
### 背景
|
|
8
|
+
pi-web 跑在 systemd 下不读 shell rc——worker 模型若只靠 env 配不进去,需要改 unit/造 EnvironmentFile。配置文件 seam 从根上消除:一个 json 管所有进程(pi-web / 命令行 pi / worker),改完重启 pi 生效。
|
|
9
|
+
|
|
10
|
+
### 用法
|
|
11
|
+
```jsonc
|
|
12
|
+
// ~/.pi/agent/mnemo.json
|
|
13
|
+
{
|
|
14
|
+
"extractModel": "zen/big-pickle",
|
|
15
|
+
"dreamModel": "zai-coding-cn/glm-5.3-flash",
|
|
16
|
+
"dreamWindow": "18:30-08:30", // 或 "" 关闭
|
|
17
|
+
"modelFallback": ["mimo-v2.5-free"] // 数组或逗号分隔字符串
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
字段名 camelCase 对齐 `MemoryConfig`;数字字段接数字。仅未知字段忽略,文件缺失/损坏 → env+默认。
|
|
21
|
+
|
|
22
|
+
## 0.1.1 — 2026-09-05
|
|
23
|
+
|
|
24
|
+
修复 worker 模型指定 bug。
|
|
25
|
+
|
|
26
|
+
### 修复
|
|
27
|
+
- spawnWorker 的模型 flag 从 `-m` 改为 `--model`(pi 无 `-m` 短写)——0.1.0 中一旦配置 `PI_MNEMO_EXTRACT_MODEL/DREAM_MODEL`,worker 会因 `Unknown option: -m` 直接启动失败。
|
|
28
|
+
|
|
3
29
|
## 0.1.0 — 2026-09-05
|
|
4
30
|
|
|
5
31
|
初始版本:mnemo(opencode 版,@qltk/mnemo 0.5.9)的 pi coding agent 移植。
|
package/README.md
CHANGED
|
@@ -62,7 +62,20 @@ ln -s /path/to/pi-mnemo ~/.pi/agent/extensions/pi-mnemo
|
|
|
62
62
|
| project | project | 代码/git 推导不出的背景、决策、约束 |
|
|
63
63
|
| reference | project | 外部系统链接 |
|
|
64
64
|
|
|
65
|
-
##
|
|
65
|
+
## 配置
|
|
66
|
+
|
|
67
|
+
推荐用配置文件 `~/.pi/agent/mnemo.json`(或 `$PI_MNEMO_CONFIG` 指定路径),优先级 **文件 > env > 默认**。改完重启 pi 生效(pi-web:`systemctl --user restart pi-web`)。
|
|
68
|
+
|
|
69
|
+
```jsonc
|
|
70
|
+
{
|
|
71
|
+
"extractModel": "zen/big-pickle", // 抽取 worker(免费档即可)
|
|
72
|
+
"dreamModel": "zai-coding-cn/glm-5.3-flash", // 整理 worker(建议大窗口)
|
|
73
|
+
"dreamWindow": "18:30-08:30", // dream 夜间窗口,"" = 全天
|
|
74
|
+
"disabled": false
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
也可用环境变量(同名语义,前缀 `PI_MNEMO_`):
|
|
66
79
|
|
|
67
80
|
| env | 默认 | 说明 |
|
|
68
81
|
|---|---|---|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qltk/pi-mnemo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Persistent memory for the pi coding agent — auto recall/extract/dream, file-based (MEMORY.md index + topic files), global + project dual scope. Port of mnemo (opencode).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/config.ts
CHANGED
|
@@ -1,12 +1,45 @@
|
|
|
1
|
-
// config.ts — configuration seam for pi-mnemo
|
|
1
|
+
// config.ts — configuration seam for pi-mnemo.
|
|
2
|
+
// Priority: ~/.pi/agent/mnemo.json (file) > env (PI_MNEMO_*) > defaults.
|
|
3
|
+
// The file is read once at process start — edit it, then restart pi (pi-web: `systemctl --user restart pi-web`).
|
|
2
4
|
|
|
3
5
|
import { join, dirname } from "node:path";
|
|
4
6
|
import { homedir } from "node:os";
|
|
5
7
|
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { readFileSync } from "node:fs";
|
|
6
9
|
import type { MemoryConfig, DreamWindow } from "./types.js";
|
|
7
10
|
|
|
8
11
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Overrides from ~/.pi/agent/mnemo.json (or $PI_MNEMO_CONFIG).
|
|
15
|
+
* Keys mirror MemoryConfig fields in camelCase, e.g.:
|
|
16
|
+
* { "extractModel": "zen/big-pickle", "dreamModel": "zai-coding-cn/glm-5.3-flash", "disabled": true }
|
|
17
|
+
* numeric fields accept numbers; dreamWindow accepts "18:30-08:30" or "" (off) or null.
|
|
18
|
+
*/
|
|
19
|
+
function loadFileOverrides(): Record<string, unknown> {
|
|
20
|
+
const file =
|
|
21
|
+
process.env.PI_MNEMO_CONFIG || join(homedir(), ".pi", "agent", "mnemo.json");
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(readFileSync(file, "utf-8"));
|
|
24
|
+
} catch {
|
|
25
|
+
return {}; // missing/malformed → env+defaults apply
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const FILE = loadFileOverrides();
|
|
30
|
+
|
|
31
|
+
function pick<T>(key: string, envKey: string, envDefault: T, isNum = false): T {
|
|
32
|
+
if (FILE[key] !== undefined && FILE[key] !== null) {
|
|
33
|
+
const v = FILE[key] as unknown;
|
|
34
|
+
return isNum ? (Number(v) as T) : (v as T);
|
|
35
|
+
}
|
|
36
|
+
if (process.env[envKey] !== undefined && process.env[envKey] !== "") {
|
|
37
|
+
const v = process.env[envKey];
|
|
38
|
+
return isNum ? (Number(v) as T) : (v as T);
|
|
39
|
+
}
|
|
40
|
+
return envDefault;
|
|
41
|
+
}
|
|
42
|
+
|
|
10
43
|
/** Parse "HH:MM" → {h,m}, null if invalid. */
|
|
11
44
|
function parseHM(s: string): { h: number; m: number } | null {
|
|
12
45
|
const mt = /^(\d{1,2}):(\d{2})$/.exec(s.trim());
|
|
@@ -16,8 +49,21 @@ function parseHM(s: string): { h: number; m: number } | null {
|
|
|
16
49
|
return { h, m };
|
|
17
50
|
}
|
|
18
51
|
|
|
19
|
-
/** Parse dream window
|
|
52
|
+
/** Parse dream window. Accepts file form "18:30-08:30" / "" (off) / null, or env pair. */
|
|
20
53
|
function parseDreamWindow(): DreamWindow | null {
|
|
54
|
+
const fileWin = FILE["dreamWindow"];
|
|
55
|
+
if (fileWin !== undefined) {
|
|
56
|
+
if (fileWin === null || fileWin === "") return null;
|
|
57
|
+
if (typeof fileWin === "string" && fileWin.includes("-")) {
|
|
58
|
+
const [s, e] = fileWin.split("-", 2);
|
|
59
|
+
const start = parseHM(s);
|
|
60
|
+
const end = parseHM(e);
|
|
61
|
+
if (start && end) {
|
|
62
|
+
return { start, end, tz: Intl.DateTimeFormat().resolvedOptions().timeZone };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return null; // malformed → window off
|
|
66
|
+
}
|
|
21
67
|
const startRaw = process.env.PI_MNEMO_DREAM_WINDOW_START ?? "18:30";
|
|
22
68
|
const endRaw = process.env.PI_MNEMO_DREAM_WINDOW_END ?? "08:30";
|
|
23
69
|
if (startRaw === "" || endRaw === "") return null; // explicitly disabled
|
|
@@ -40,51 +86,41 @@ function parseDreamWindow(): DreamWindow | null {
|
|
|
40
86
|
return { start, end, tz };
|
|
41
87
|
}
|
|
42
88
|
|
|
43
|
-
/** Parse comma-separated list (drop empties). */
|
|
44
|
-
function
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
/** Read env number (empty/missing/non-numeric → default; explicit 0 honored). */
|
|
49
|
-
function envNum(key: string, def: number): number {
|
|
50
|
-
const v = process.env[key];
|
|
51
|
-
if (v === undefined || v === "") return def;
|
|
52
|
-
const n = Number(v);
|
|
53
|
-
return Number.isFinite(n) ? n : def;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function envBool(key: string): boolean {
|
|
57
|
-
const v = process.env[key];
|
|
58
|
-
return v === "1" || v === "true" || v === "yes";
|
|
89
|
+
/** Parse comma-separated list (drop empties). File form accepts a JSON array directly. */
|
|
90
|
+
function pickList(raw: string | unknown[]): string[] {
|
|
91
|
+
if (Array.isArray(raw)) return raw.map(String).filter(Boolean);
|
|
92
|
+
return String(raw ?? "").split(",").map((x) => x.trim()).filter(Boolean);
|
|
59
93
|
}
|
|
60
94
|
|
|
61
95
|
export const CONFIG: MemoryConfig = {
|
|
62
96
|
memoryRoot:
|
|
63
|
-
|
|
97
|
+
(pick("memoryRoot", "PI_MNEMO_ROOT", "") as string) ||
|
|
64
98
|
join(homedir(), ".pi", "agent", "mnemo"),
|
|
65
99
|
promptsDir:
|
|
66
|
-
|
|
100
|
+
(pick("promptsDir", "PI_MNEMO_PROMPTS_DIR", "") as string) ||
|
|
101
|
+
join(__dirname, "..", "prompts"),
|
|
67
102
|
|
|
68
103
|
// Headless worker model ("provider/id"; empty = pi's default model at spawn time)
|
|
69
|
-
extractModel:
|
|
70
|
-
dreamModel:
|
|
71
|
-
modelFallback:
|
|
72
|
-
workerTools:
|
|
104
|
+
extractModel: pick("extractModel", "PI_MNEMO_EXTRACT_MODEL", "") as string,
|
|
105
|
+
dreamModel: pick("dreamModel", "PI_MNEMO_DREAM_MODEL", "") as string,
|
|
106
|
+
modelFallback: pickList(pick("modelFallback", "PI_MNEMO_MODEL_FALLBACK", "") as string),
|
|
107
|
+
workerTools: pick("workerTools", "PI_MNEMO_WORKER_TOOLS", "read,grep,glob,bash") as string,
|
|
73
108
|
|
|
74
|
-
dreamIntervalMs:
|
|
109
|
+
dreamIntervalMs: pick("dreamIntervalMs", "PI_MNEMO_DREAM_INTERVAL_MS", 24 * 60 * 60 * 1000, true) as number,
|
|
75
110
|
dreamWindow: parseDreamWindow(),
|
|
76
111
|
|
|
77
|
-
extractMinNewMessages:
|
|
78
|
-
extractMinIntervalMs:
|
|
112
|
+
extractMinNewMessages: pick("extractMinNewMessages", "PI_MNEMO_EXTRACT_MIN_NEW_MESSAGES", 5, true) as number,
|
|
113
|
+
extractMinIntervalMs: pick("extractMinIntervalMs", "PI_MNEMO_EXTRACT_MIN_INTERVAL_MS", 30 * 60 * 1000, true) as number,
|
|
79
114
|
|
|
80
|
-
dreamBacklogDays:
|
|
81
|
-
dreamBacklogPerIdle:
|
|
115
|
+
dreamBacklogDays: pick("dreamBacklogDays", "PI_MNEMO_DREAM_BACKLOG_DAYS", 2, true) as number,
|
|
116
|
+
dreamBacklogPerIdle: pick("dreamBacklogPerIdle", "PI_MNEMO_DREAM_BACKLOG_PER_IDLE", 1, true) as number,
|
|
82
117
|
|
|
83
|
-
pruneAgeDays:
|
|
84
|
-
coldStartDays:
|
|
85
|
-
topicSoftMaxKB:
|
|
86
|
-
logMaxBytes:
|
|
118
|
+
pruneAgeDays: pick("pruneAgeDays", "PI_MNEMO_PRUNE_AGE_DAYS", 30, true) as number,
|
|
119
|
+
coldStartDays: pick("coldStartDays", "PI_MNEMO_COLD_START_DAYS", 14, true) as number,
|
|
120
|
+
topicSoftMaxKB: pick("topicSoftMaxKB", "PI_MNEMO_TOPIC_SOFT_MAX_KB", 8, true) as number,
|
|
121
|
+
logMaxBytes: pick("logMaxBytes", "PI_MNEMO_LOG_MAX_BYTES", 1024 * 1024, true) as number,
|
|
87
122
|
|
|
88
|
-
workerTimeoutMs:
|
|
89
|
-
disabled:
|
|
123
|
+
workerTimeoutMs: pick("workerTimeoutMs", "PI_MNEMO_WORKER_TIMEOUT_MS", 10 * 60 * 1000, true) as number,
|
|
124
|
+
disabled: pick<string>("disabled", "PI_MNEMO_DISABLED", "0") === "1" ||
|
|
125
|
+
String(FILE["disabled"]) === "true",
|
|
90
126
|
};
|
package/src/orchestrator.ts
CHANGED
|
@@ -53,7 +53,7 @@ export function spawnWorker(opts: WorkerOpts): void {
|
|
|
53
53
|
"--no-context-files", // skip AGENTS.md discovery — not needed for memory work
|
|
54
54
|
"-t", opts.tools || CONFIG.workerTools,
|
|
55
55
|
];
|
|
56
|
-
if (opts.model) args.push("
|
|
56
|
+
if (opts.model) args.push("--model", opts.model); // pi 的 flag 是 --model(无 -m 短写)
|
|
57
57
|
log("spawn worker:", piBin(), args.join(" "));
|
|
58
58
|
|
|
59
59
|
const child = spawn(piBin(), args, {
|