@xaccefy/pi-casefile 0.9.0 → 0.9.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.
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Worker-thread entry for heavy ledger reads (suggestChains, writeCaseContext).
3
+ *
4
+ * Runs in a dedicated thread so an O(n²) chain scan or a multi-megabyte
5
+ * context-bundle build never blocks the agent's event loop. Each call spawns
6
+ * a fresh worker — rare operations, no lifecycle to manage, no stale state.
7
+ * The worker opens its OWN connection to the same WAL database (read-mostly
8
+ * work + one small reportPath write); WAL + busy_timeout make multi-connection
9
+ * access safe.
10
+ */
11
+
12
+ import { parentPort, workerData } from "node:worker_threads";
13
+ import { suggestChains, writeCaseContext } from "./ledger.ts";
14
+ import { setScratchpadRoot } from "./scratchpad.ts";
15
+
16
+ process.env.PI_CASEFILE_PATH = workerData.casefilePath as string;
17
+ setScratchpadRoot(workerData.workspaceRoot as string | undefined);
18
+
19
+ type WorkerRequest =
20
+ | { op: "suggestChains"; caseId?: string }
21
+ | { op: "writeCaseContext"; id: string };
22
+
23
+ parentPort?.on("message", (req: WorkerRequest) => {
24
+ try {
25
+ if (req.op === "suggestChains") {
26
+ parentPort?.postMessage({ ok: true, result: suggestChains(req.caseId) });
27
+ } else if (req.op === "writeCaseContext") {
28
+ parentPort?.postMessage({ ok: true, result: writeCaseContext(req.id) });
29
+ } else {
30
+ parentPort?.postMessage({ ok: false, error: `unknown ledger worker op` });
31
+ }
32
+ } catch (e) {
33
+ parentPort?.postMessage({ ok: false, error: (e as Error).message });
34
+ }
35
+ });
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Async offload for the two heavy ledger reads — main-thread side.
3
+ *
4
+ * suggestChains is O(rules × cases²) over the whole ledger; writeCaseContext
5
+ * reads every artifact of every matching scratchpad run and builds a
6
+ * multi-hundred-KB bundle. Both ran synchronously inside async tool handlers,
7
+ * stalling the event loop. Each call here spawns a short-lived worker thread
8
+ * (ledger-worker-entry.ts) and falls back to the inline sync function when
9
+ * the worker cannot run (Node < 22.18 has no default type stripping for the
10
+ * TS entry) or times out. Writes stay sync on the main thread: they are
11
+ * single-row upserts, bounded by the 5s busy_timeout.
12
+ */
13
+
14
+ import { Worker } from "node:worker_threads";
15
+ import {
16
+ type CaseContextResult,
17
+ type ChainSuggestion,
18
+ getCasefilePath,
19
+ suggestChains,
20
+ writeCaseContext,
21
+ } from "./ledger.ts";
22
+ import { detectWorkspaceRoot } from "./scratchpad.ts";
23
+
24
+ type WorkerResponse = { ok: true; result: unknown } | { ok: false; error: string };
25
+
26
+ const WORKER_TIMEOUT_MS = 30_000;
27
+
28
+ function runInLedgerWorker(
29
+ request:
30
+ | {
31
+ op: "suggestChains";
32
+ caseId?: string;
33
+ }
34
+ | {
35
+ op: "writeCaseContext";
36
+ id: string;
37
+ },
38
+ ): Promise<unknown> {
39
+ const { promise, resolve, reject } = Promise.withResolvers<unknown>();
40
+ const worker = new Worker(new URL("./ledger-worker-entry.ts", import.meta.url), {
41
+ workerData: { casefilePath: getCasefilePath(), workspaceRoot: detectWorkspaceRoot() },
42
+ });
43
+ const timer = setTimeout(() => {
44
+ void worker.terminate();
45
+ reject(new Error("ledger worker timed out"));
46
+ }, WORKER_TIMEOUT_MS);
47
+ worker.once("message", (msg: WorkerResponse) => {
48
+ clearTimeout(timer);
49
+ void worker.terminate();
50
+ if (msg.ok) resolve(msg.result);
51
+ else reject(new Error(msg.error));
52
+ });
53
+ worker.once("error", (e) => {
54
+ clearTimeout(timer);
55
+ reject(e);
56
+ });
57
+ worker.postMessage(request);
58
+ return promise;
59
+ }
60
+
61
+ /** suggestChains on a worker thread; inline fallback keeps behavior identical. */
62
+ export async function suggestChainsAsync(caseId?: string): Promise<ChainSuggestion[]> {
63
+ try {
64
+ return (await runInLedgerWorker({ op: "suggestChains", caseId })) as ChainSuggestion[];
65
+ } catch {
66
+ return suggestChains(caseId);
67
+ }
68
+ }
69
+
70
+ /** writeCaseContext on a worker thread; inline fallback keeps behavior identical. */
71
+ export async function writeCaseContextAsync(id: string): Promise<CaseContextResult> {
72
+ try {
73
+ return (await runInLedgerWorker({ op: "writeCaseContext", id })) as CaseContextResult;
74
+ } catch {
75
+ return writeCaseContext(id);
76
+ }
77
+ }