@qltk/pi-mnemo 0.2.1 → 0.2.2

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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.2 — 2026-09-05
4
+
5
+ worker 生命周期高密度诊断(排查 pi-web 扩展宿主内 spawn 的 worker 无声死亡)。
6
+
7
+ ### 诊断
8
+ - spawnWorker 全链路事件日志:spawn 返回(pid)/ spawn 事件 / error / exit / close / stdin flush。
9
+ - 5s/30s/60s 存活探针(/proc/<pid> 检查),区分"进程没起来"vs"起来即死"。
10
+ - 目的:0.2.0 后 pi-web 宿主内 spawn 的 worker 三次无声消失(无产物/无 exit 日志/无进程/stderr 空),同参数同环境手动复现均成功(107s 完整抽取)。剩余变量=扩展宿主执行上下文。
3
11
  ## 0.2.1 — 2026-09-05
4
12
 
5
13
  worker 可观测性:stderr 落盘。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qltk/pi-mnemo",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
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",
@@ -8,7 +8,7 @@
8
8
  // so no transcript copying is needed — the prompt just points at the session file.
9
9
 
10
10
  import { spawn } from "node:child_process";
11
- import { openSync, statSync, renameSync } from "node:fs";
11
+ import { openSync, statSync, renameSync, existsSync } from "node:fs";
12
12
  import { join, basename } from "node:path";
13
13
  import { CONFIG } from "./config.js";
14
14
  import { projectDir, readPrompt } from "./io.js";
@@ -75,11 +75,29 @@ export function spawnWorker(opts: WorkerOpts): void {
75
75
  stdio: ["pipe", "ignore", errFd],
76
76
  env: { ...process.env, PI_MNEMO_WORKER: "1" },
77
77
  });
78
+ log("worker spawned pid=", child.pid, "spawnflags ok");
79
+ child.on("spawn", () => log("worker spawn-event pid=", child.pid));
80
+ child.on("error", (e) => log("worker error:", e.message));
81
+ child.on("exit", (code, signal) => log("worker exited", child.pid, "code", code, "signal", signal));
82
+ child.on("close", (code, signal) => log("worker closed", child.pid, "code", code, "signal", signal));
83
+ // 早期存活探针:区分 "进程没起来" vs "起来即死"
84
+ for (const t of [5, 30, 60]) {
85
+ const probe = setTimeout(() => {
86
+ const alive = child.pid ? existsSync(`/proc/${child.pid}`) : false;
87
+ log(`worker probe ${t}s pid=`, child.pid, "alive=", alive, "exitCode=", child.exitCode, "killed=", child.killed);
88
+ }, t * 1000);
89
+ probe.unref?.();
90
+ }
78
91
  child.unref();
79
92
 
80
- child.stdin?.on("error", () => { /* EPIPE — worker may have exited early */ });
81
- child.stdin?.write(opts.prompt);
82
- child.stdin?.end();
93
+ child.stdin?.on("error", (e) => log("worker stdin error:", e?.message ?? String(e)));
94
+ try {
95
+ child.stdin?.write(opts.prompt);
96
+ child.stdin?.end();
97
+ log("worker stdin flushed", opts.prompt.length, "bytes");
98
+ } catch (e: any) {
99
+ log("worker stdin write threw:", e?.message ?? e);
100
+ }
83
101
 
84
102
  // stuck-timeout guard: kill after workerTimeoutMs
85
103
  const pid = child.pid;
@@ -90,9 +108,6 @@ export function spawnWorker(opts: WorkerOpts): void {
90
108
  } catch { /* already exited */ }
91
109
  }, CONFIG.workerTimeoutMs);
92
110
  timer.unref();
93
-
94
- child.on("error", (e) => log("worker spawn error:", e.message));
95
- child.on("exit", (code, signal) => log("worker exited", pid, "code", code, "signal", signal));
96
111
  }
97
112
 
98
113
  export interface OrchestratorOpts {