@nguyenthdat/opencode-memory 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.
Files changed (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +170 -0
  3. package/THIRD_PARTY_NOTICES.md +14 -0
  4. package/dist/contracts.d.ts +96 -0
  5. package/dist/contracts.d.ts.map +1 -0
  6. package/dist/contracts.js +46 -0
  7. package/dist/contracts.js.map +1 -0
  8. package/dist/generated/memory_pb.d.ts +227 -0
  9. package/dist/generated/memory_pb.d.ts.map +1 -0
  10. package/dist/generated/memory_pb.js +112 -0
  11. package/dist/generated/memory_pb.js.map +1 -0
  12. package/dist/index.d.ts +15 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +24 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/plugin.d.ts +7 -0
  17. package/dist/plugin.d.ts.map +1 -0
  18. package/dist/plugin.js +665 -0
  19. package/dist/plugin.js.map +1 -0
  20. package/dist/policy.d.ts +19 -0
  21. package/dist/policy.d.ts.map +1 -0
  22. package/dist/policy.js +125 -0
  23. package/dist/policy.js.map +1 -0
  24. package/dist/protocol.d.ts +31 -0
  25. package/dist/protocol.d.ts.map +1 -0
  26. package/dist/protocol.js +220 -0
  27. package/dist/protocol.js.map +1 -0
  28. package/dist/session-context.d.ts +40 -0
  29. package/dist/session-context.d.ts.map +1 -0
  30. package/dist/session-context.js +92 -0
  31. package/dist/session-context.js.map +1 -0
  32. package/dist/shared-markdown.d.ts +11 -0
  33. package/dist/shared-markdown.d.ts.map +1 -0
  34. package/dist/shared-markdown.js +178 -0
  35. package/dist/shared-markdown.js.map +1 -0
  36. package/dist/sidecar-client.d.ts +39 -0
  37. package/dist/sidecar-client.d.ts.map +1 -0
  38. package/dist/sidecar-client.js +397 -0
  39. package/dist/sidecar-client.js.map +1 -0
  40. package/dist/validation.d.ts +2 -0
  41. package/dist/validation.d.ts.map +1 -0
  42. package/dist/validation.js +29 -0
  43. package/dist/validation.js.map +1 -0
  44. package/notices/ZVEC_NOTICE +95 -0
  45. package/package.json +90 -0
  46. package/rules/flow.md +16 -0
@@ -0,0 +1,11 @@
1
+ import type { MemoryRecord, SharedMemoryRecord } from "./contracts.js";
2
+ export declare const SHARED_MEMORY_RELATIVE_DIR = ".opencode/memory";
3
+ export declare function loadSharedMemories(worktree: string): Promise<{
4
+ records: SharedMemoryRecord[];
5
+ signature: string;
6
+ }>;
7
+ export declare function parseSharedMemory(source: string, input: string): SharedMemoryRecord;
8
+ export declare function writeSharedMemory(worktree: string, memory: MemoryRecord): Promise<string>;
9
+ export declare function ensureRealDirectory(path: string): Promise<void>;
10
+ export declare function isPathWithin(root: string, candidate: string): boolean;
11
+ //# sourceMappingURL=shared-markdown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared-markdown.d.ts","sourceRoot":"","sources":["../opencode-memory/src/shared-markdown.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAGvE,eAAO,MAAM,0BAA0B,qBAAqB,CAAC;AAI7D,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;IAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAiD/D;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,kBAAkB,CAiDnF;AAED,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAsC/F;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAUrE;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAGrE"}
@@ -0,0 +1,178 @@
1
+ import { createHash } from "node:crypto";
2
+ import { lstat, readFile, readdir, realpath, rename, stat, writeFile, mkdir, } from "node:fs/promises";
3
+ import { isAbsolute, relative, resolve } from "node:path";
4
+ import YAML from "yaml";
5
+ import { MEMORY_KINDS } from "./contracts.js";
6
+ export const SHARED_MEMORY_RELATIVE_DIR = ".opencode/memory";
7
+ const MAX_SHARED_FILES = 200;
8
+ const MAX_SHARED_FILE_BYTES = 64 * 1_024;
9
+ export async function loadSharedMemories(worktree) {
10
+ const directory = resolve(worktree, SHARED_MEMORY_RELATIVE_DIR);
11
+ const canonicalRoot = await realpath(worktree);
12
+ let entries;
13
+ try {
14
+ const directoryInfo = await lstat(directory);
15
+ if (directoryInfo.isSymbolicLink() || !directoryInfo.isDirectory()) {
16
+ throw new Error("Shared memory directory must be a real directory, not a symlink");
17
+ }
18
+ const canonicalDirectory = await realpath(directory);
19
+ if (!isPathWithin(canonicalRoot, canonicalDirectory)) {
20
+ throw new Error("Shared memory directory escaped the project worktree");
21
+ }
22
+ entries = await readdir(directory, { withFileTypes: true });
23
+ }
24
+ catch (error) {
25
+ if (isNodeError(error) && error.code === "ENOENT") {
26
+ return { records: [], signature: createHash("sha256").digest("hex") };
27
+ }
28
+ throw error;
29
+ }
30
+ const names = entries
31
+ .filter((entry) => entry.isFile() && entry.name.endsWith(".md"))
32
+ .map((entry) => entry.name)
33
+ .sort();
34
+ if (names.length > MAX_SHARED_FILES) {
35
+ throw new Error(`At most ${MAX_SHARED_FILES} shared memory files are allowed`);
36
+ }
37
+ const hash = createHash("sha256");
38
+ const records = [];
39
+ for (const name of names) {
40
+ const path = resolve(directory, name);
41
+ const linkInfo = await lstat(path);
42
+ if (linkInfo.isSymbolicLink() || !linkInfo.isFile()) {
43
+ throw new Error(`Shared memory must be a regular file: ${name}`);
44
+ }
45
+ const canonicalPath = await realpath(path);
46
+ if (!isPathWithin(canonicalRoot, canonicalPath)) {
47
+ throw new Error(`Shared memory file escaped the project worktree: ${name}`);
48
+ }
49
+ const info = await stat(canonicalPath);
50
+ if (info.size > MAX_SHARED_FILE_BYTES) {
51
+ throw new Error(`Shared memory file exceeds ${MAX_SHARED_FILE_BYTES} bytes: ${name}`);
52
+ }
53
+ const source = `${SHARED_MEMORY_RELATIVE_DIR}/${name}`;
54
+ const content = await readFile(canonicalPath, "utf8");
55
+ hash.update(source).update("\0").update(content).update("\0");
56
+ records.push(parseSharedMemory(source, content));
57
+ }
58
+ return { records, signature: hash.digest("hex") };
59
+ }
60
+ export function parseSharedMemory(source, input) {
61
+ if (!input.startsWith("---\n")) {
62
+ throw new Error(`Shared memory is missing YAML frontmatter: ${source}`);
63
+ }
64
+ const end = input.indexOf("\n---\n", 4);
65
+ if (end < 0) {
66
+ throw new Error(`Shared memory has malformed YAML frontmatter: ${source}`);
67
+ }
68
+ const metadata = YAML.parse(input.slice(4, end));
69
+ const content = input.slice(end + 5).trim();
70
+ if (!isObject(metadata))
71
+ throw new Error(`Invalid shared memory: ${source}`);
72
+ const allowed = new Set([
73
+ "schema_version",
74
+ "id",
75
+ "title",
76
+ "kind",
77
+ "importance",
78
+ "tags",
79
+ "code_paths",
80
+ "updated_at_ms",
81
+ ]);
82
+ if (Object.keys(metadata).some((key) => !allowed.has(key))) {
83
+ throw new Error(`Shared memory has unknown fields: ${source}`);
84
+ }
85
+ if (metadata.schema_version !== 1 ||
86
+ typeof metadata.title !== "string" ||
87
+ metadata.title.length === 0 ||
88
+ metadata.title.length > 160 ||
89
+ !MEMORY_KINDS.includes(metadata.kind) ||
90
+ typeof metadata.importance !== "number" ||
91
+ metadata.importance < 0 ||
92
+ metadata.importance > 1 ||
93
+ !isStringArray(metadata.tags, 12, 64) ||
94
+ !isStringArray(metadata.code_paths, 12, 512) ||
95
+ content.length === 0 ||
96
+ content.length > 6_000) {
97
+ throw new Error(`Shared memory fields are invalid: ${source}`);
98
+ }
99
+ return {
100
+ source,
101
+ title: metadata.title,
102
+ content,
103
+ kind: metadata.kind,
104
+ importance: metadata.importance,
105
+ tags: metadata.tags,
106
+ code_paths: metadata.code_paths,
107
+ };
108
+ }
109
+ export async function writeSharedMemory(worktree, memory) {
110
+ const canonicalRoot = await realpath(worktree);
111
+ const opencodeDirectory = resolve(canonicalRoot, ".opencode");
112
+ await ensureRealDirectory(opencodeDirectory);
113
+ const directory = resolve(opencodeDirectory, "memory");
114
+ await ensureRealDirectory(directory);
115
+ const canonicalDirectory = await realpath(directory);
116
+ if (!isPathWithin(canonicalRoot, canonicalDirectory)) {
117
+ throw new Error("Shared memory directory escaped the project worktree");
118
+ }
119
+ const destination = resolve(directory, `${memory.id}.md`);
120
+ try {
121
+ const destinationInfo = await lstat(destination);
122
+ if (destinationInfo.isSymbolicLink() || !destinationInfo.isFile()) {
123
+ throw new Error("Shared memory destination must be a regular file");
124
+ }
125
+ }
126
+ catch (error) {
127
+ if (!isNodeError(error) || error.code !== "ENOENT")
128
+ throw error;
129
+ }
130
+ const relativePath = relative(canonicalRoot, destination).replaceAll("\\", "/");
131
+ if (!relativePath.startsWith(`${SHARED_MEMORY_RELATIVE_DIR}/`)) {
132
+ throw new Error("Shared memory destination escaped the project directory");
133
+ }
134
+ const frontmatter = YAML.stringify({
135
+ schema_version: 1,
136
+ id: memory.id,
137
+ title: memory.title,
138
+ kind: memory.kind,
139
+ importance: memory.importance,
140
+ tags: memory.tags,
141
+ code_paths: memory.code_anchors.map((anchor) => anchor.path),
142
+ updated_at_ms: memory.updated_at_ms,
143
+ });
144
+ const output = `---\n${frontmatter}---\n\n${memory.content.trim()}\n`;
145
+ const temporary = `${destination}.tmp-${process.pid}-${Date.now()}`;
146
+ await writeFile(temporary, output, { encoding: "utf8", flag: "wx", mode: 0o600 });
147
+ await rename(temporary, destination);
148
+ return relativePath;
149
+ }
150
+ export async function ensureRealDirectory(path) {
151
+ try {
152
+ const info = await lstat(path);
153
+ if (info.isSymbolicLink() || !info.isDirectory()) {
154
+ throw new Error(`Expected a real directory, not a symlink: ${path}`);
155
+ }
156
+ }
157
+ catch (error) {
158
+ if (!isNodeError(error) || error.code !== "ENOENT")
159
+ throw error;
160
+ await mkdir(path, { recursive: false, mode: 0o700 });
161
+ }
162
+ }
163
+ export function isPathWithin(root, candidate) {
164
+ const path = relative(root, candidate);
165
+ return path === "" || (!path.startsWith("..") && !isAbsolute(path));
166
+ }
167
+ function isObject(value) {
168
+ return typeof value === "object" && value !== null && !Array.isArray(value);
169
+ }
170
+ function isStringArray(value, maxItems, maxLength) {
171
+ return (Array.isArray(value) &&
172
+ value.length <= maxItems &&
173
+ value.every((item) => typeof item === "string" && item.length > 0 && item.length <= maxLength));
174
+ }
175
+ function isNodeError(error) {
176
+ return error instanceof Error && "code" in error;
177
+ }
178
+ //# sourceMappingURL=shared-markdown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared-markdown.js","sourceRoot":"","sources":["../opencode-memory/src/shared-markdown.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import type { ChildProcessWithoutNullStreams } from "node:child_process";
2
+ import type { MemoryMethod } from "./protocol.js";
3
+ export declare const REQUEST_TIMEOUT_MS = 300000;
4
+ export declare const INITIALIZATION_TIMEOUT_MS: number;
5
+ export declare const MAX_REQUEST_BYTES: number;
6
+ export declare const MAX_RESPONSE_BYTES: number;
7
+ export type SpawnFn = (binary: string, args: string[], opts: {
8
+ cwd: string;
9
+ detached: boolean;
10
+ env: NodeJS.ProcessEnv | undefined;
11
+ stdio: ["pipe", "pipe", "pipe"];
12
+ }) => ChildProcessWithoutNullStreams;
13
+ export declare function resolveNativeMemoryBinary(root: string): string;
14
+ export declare class NativeMemoryClient {
15
+ private readonly root;
16
+ private readonly worktree;
17
+ private process;
18
+ private disposed;
19
+ private nextID;
20
+ private pending;
21
+ private generation;
22
+ private handshake;
23
+ private readonly spawnFn;
24
+ private readonly usingSpawnOverride;
25
+ private readonly requestTimeoutMs;
26
+ constructor(root: string, worktree: string, spawnOverride?: SpawnFn, requestTimeoutMs?: number);
27
+ request<T>(method: MemoryMethod, params?: unknown, signal?: AbortSignal): Promise<T>;
28
+ dispose(): Promise<void>;
29
+ private sendRequest;
30
+ private waitForHandshake;
31
+ private ensureHandshake;
32
+ private start;
33
+ private handleFrame;
34
+ private finishPending;
35
+ private rejectGeneration;
36
+ private failProcess;
37
+ private isCurrentAndRunning;
38
+ }
39
+ //# sourceMappingURL=sidecar-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sidecar-client.d.ts","sourceRoot":"","sources":["../opencode-memory/src/sidecar-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,oBAAoB,CAAC;AAOzE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAIlD,eAAO,MAAM,kBAAkB,SAAU,CAAC;AAC1C,eAAO,MAAM,yBAAyB,QAAc,CAAC;AACrD,eAAO,MAAM,iBAAiB,QAAW,CAAC;AAC1C,eAAO,MAAM,kBAAkB,QAAW,CAAC;AAe3C,MAAM,MAAM,OAAO,GAAG,CACpB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,EAAE;IACJ,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,OAAO,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,SAAS,CAAC;IACnC,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC,KACE,8BAA8B,CAAC;AAEpC,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA4B9D;AAwBD,qBAAa,kBAAkB;IAY3B,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAZ3B,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,OAAO,CAA8D;IAC7E,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,SAAS,CAA+B;IAChD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAClC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAU;IAC7C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;gBAGvB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EACjC,aAAa,CAAC,EAAE,OAAO,EACvB,gBAAgB,CAAC,EAAE,MAAM;IASrB,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAE,OAAY,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC;IA8BxF,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA2C9B,OAAO,CAAC,WAAW;YA4DL,gBAAgB;IAkC9B,OAAO,CAAC,eAAe;IAkCvB,OAAO,CAAC,KAAK;IA6Eb,OAAO,CAAC,WAAW;IA6BnB,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,gBAAgB;IAQxB,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,mBAAmB;CAO5B"}
@@ -0,0 +1,397 @@
1
+ import { spawn as nodeSpawn } from "node:child_process";
2
+ import { existsSync, realpathSync } from "node:fs";
3
+ import { createRequire } from "node:module";
4
+ import { resolve } from "node:path";
5
+ import { decodeResponse, DelimitedFrameDecoder, encodeRequest } from "./protocol.js";
6
+ const MiB = 1024 * 1024;
7
+ export const REQUEST_TIMEOUT_MS = 300_000;
8
+ export const INITIALIZATION_TIMEOUT_MS = 30 * 60_000;
9
+ export const MAX_REQUEST_BYTES = 32 * MiB;
10
+ export const MAX_RESPONSE_BYTES = 32 * MiB;
11
+ const MAX_STDERR_BYTES = 8_192;
12
+ const MAX_HANDSHAKE_RESTARTS = 1;
13
+ const SUPPORTED_RPC_VERSION = 2;
14
+ const require = createRequire(import.meta.url);
15
+ const NATIVE_PACKAGES = {
16
+ "darwin-arm64": "@nguyenthdat/opencode-memory-darwin-arm64",
17
+ "darwin-x64": "@nguyenthdat/opencode-memory-darwin-x64",
18
+ "linux-arm64": "@nguyenthdat/opencode-memory-linux-arm64-gnu",
19
+ "linux-x64": "@nguyenthdat/opencode-memory-linux-x64-gnu",
20
+ "win32-x64": "@nguyenthdat/opencode-memory-win32-x64-msvc",
21
+ };
22
+ export function resolveNativeMemoryBinary(root) {
23
+ const override = process.env.OPENCODE_NATIVE_MEMORY_BIN;
24
+ const binaryName = process.platform === "win32" ? "opencode-memory.exe" : "opencode-memory";
25
+ const packaged = resolvePackagedBinary(binaryName);
26
+ const candidates = override
27
+ ? [resolve(override)]
28
+ : [
29
+ resolve(root, "target", "release", binaryName),
30
+ resolve(root, "target", "debug", binaryName),
31
+ ...(packaged ? [packaged] : []),
32
+ ];
33
+ for (const candidate of candidates) {
34
+ if (!existsSync(candidate))
35
+ continue;
36
+ const binary = realpathSync(candidate);
37
+ if (!override && process.platform !== "win32") {
38
+ const library = resolve(binary, "..", "memory-libs", process.platform === "darwin" ? "libzvec_c_api.dylib" : "libzvec_c_api.so");
39
+ if (!existsSync(library))
40
+ continue;
41
+ }
42
+ return binary;
43
+ }
44
+ throw new Error(`Native memory binary was not found. Reinstall with optional dependencies or run \`bun run build:native:release\`. Checked: ${candidates.join(", ")}`);
45
+ }
46
+ function resolvePackagedBinary(binaryName) {
47
+ const packageName = NATIVE_PACKAGES[`${process.platform}-${process.arch}`];
48
+ if (!packageName)
49
+ return undefined;
50
+ try {
51
+ return require.resolve(`${packageName}/bin/${binaryName}`);
52
+ }
53
+ catch {
54
+ return undefined;
55
+ }
56
+ }
57
+ class ProcessReplacedError extends Error {
58
+ }
59
+ export class NativeMemoryClient {
60
+ root;
61
+ worktree;
62
+ process;
63
+ disposed = false;
64
+ nextID = 1;
65
+ pending = new Map();
66
+ generation = 0;
67
+ handshake;
68
+ spawnFn;
69
+ usingSpawnOverride;
70
+ requestTimeoutMs;
71
+ constructor(root, worktree, spawnOverride, requestTimeoutMs) {
72
+ this.root = root;
73
+ this.worktree = worktree;
74
+ this.spawnFn = spawnOverride ?? nodeSpawn;
75
+ this.usingSpawnOverride = spawnOverride !== undefined;
76
+ this.requestTimeoutMs = requestTimeoutMs ?? REQUEST_TIMEOUT_MS;
77
+ }
78
+ // ---- Public API ---------------------------------------------------------
79
+ async request(method, params = {}, signal) {
80
+ if (this.disposed)
81
+ throw new Error("Native memory client is disposed");
82
+ if (signal?.aborted)
83
+ throw new Error("Native memory request was cancelled");
84
+ if (method === "shutdown") {
85
+ return await this.sendRequest(this.start(), method, params, signal);
86
+ }
87
+ for (let restart = 0; restart <= MAX_HANDSHAKE_RESTARTS; restart += 1) {
88
+ const process = this.start();
89
+ try {
90
+ await this.waitForHandshake(process, signal);
91
+ }
92
+ catch (error) {
93
+ if (error instanceof ProcessReplacedError) {
94
+ if (restart < MAX_HANDSHAKE_RESTARTS)
95
+ continue;
96
+ throw new Error("Native memory sidecar exited repeatedly during protocol handshake");
97
+ }
98
+ throw error;
99
+ }
100
+ if (signal?.aborted)
101
+ throw new Error("Native memory request was cancelled");
102
+ if (!this.isCurrentAndRunning(process)) {
103
+ if (restart < MAX_HANDSHAKE_RESTARTS)
104
+ continue;
105
+ throw new Error("Native memory sidecar exited repeatedly during protocol handshake");
106
+ }
107
+ return await this.sendRequest(process, method, params, signal);
108
+ }
109
+ throw new Error("Native memory handshake retry limit was exhausted");
110
+ }
111
+ async dispose() {
112
+ this.disposed = true;
113
+ const ps = this.process;
114
+ if (!ps)
115
+ return;
116
+ // If process already exited, just clean up
117
+ if (ps.child.exitCode !== null || ps.child.signalCode !== null) {
118
+ this.process = undefined;
119
+ this.rejectGeneration(ps.generation, new Error("Native memory client stopped"));
120
+ return;
121
+ }
122
+ // Register close before sending shutdown to avoid missing the event
123
+ const closePromise = new Promise((resolveClose) => {
124
+ ps.child.once("close", () => resolveClose());
125
+ });
126
+ const forceKill = setTimeout(() => {
127
+ stopProcessTree(ps.child, "SIGKILL");
128
+ }, 1_000);
129
+ forceKill.unref?.();
130
+ // Send shutdown through internal path, bypassing disposed check
131
+ try {
132
+ await this.sendRequest(ps, "shutdown", {});
133
+ }
134
+ catch {
135
+ // Process teardown below is authoritative.
136
+ }
137
+ ps.child.stdin.end();
138
+ try {
139
+ await closePromise;
140
+ }
141
+ finally {
142
+ clearTimeout(forceKill);
143
+ if (this.process === ps) {
144
+ this.process = undefined;
145
+ }
146
+ this.rejectGeneration(ps.generation, new Error("Native memory client stopped"));
147
+ }
148
+ }
149
+ // ---- Internal: sending requests to a captured process --------------------
150
+ sendRequest(process, method, params = {}, signal, timeoutMs = this.requestTimeoutMs) {
151
+ if (!this.isCurrentAndRunning(process)) {
152
+ throw new ProcessReplacedError("Native memory process changed before the request was sent");
153
+ }
154
+ if (signal?.aborted)
155
+ throw new Error("Native memory request was cancelled");
156
+ const id = this.nextID++;
157
+ const payload = encodeRequest(id, method, params);
158
+ const payloadBytes = payload.byteLength;
159
+ if (payloadBytes > MAX_REQUEST_BYTES) {
160
+ throw new Error(`Native memory request payload exceeds ${MAX_REQUEST_BYTES} bytes (was ${payloadBytes})`);
161
+ }
162
+ return new Promise((resolveRequest, rejectRequest) => {
163
+ const timeout = timeoutMs;
164
+ const timer = setTimeout(() => {
165
+ const active = this.pending.get(id);
166
+ if (!active)
167
+ return;
168
+ this.finishPending(id, active);
169
+ rejectRequest(new Error(`Native memory ${method} timed out after ${timeout} ms`));
170
+ }, timeout);
171
+ timer.unref?.();
172
+ const entry = {
173
+ resolve: (value) => resolveRequest(value),
174
+ reject: rejectRequest,
175
+ timer,
176
+ signal,
177
+ generation: process.generation,
178
+ };
179
+ if (signal) {
180
+ entry.abort = () => {
181
+ if (!this.pending.delete(id))
182
+ return;
183
+ clearTimeout(timer);
184
+ rejectRequest(new Error(`Native memory ${method} was cancelled`));
185
+ };
186
+ signal.addEventListener("abort", entry.abort, { once: true });
187
+ }
188
+ this.pending.set(id, entry);
189
+ process.child.stdin.write(payload, (error) => {
190
+ if (!error)
191
+ return;
192
+ const active = this.pending.get(id);
193
+ if (!active)
194
+ return;
195
+ this.finishPending(id, active);
196
+ active.reject(new Error(`Cannot write native memory request: ${error.message}`));
197
+ });
198
+ });
199
+ }
200
+ // ---- Internal: handshake ------------------------------------------------
201
+ async waitForHandshake(process, signal) {
202
+ if (signal?.aborted) {
203
+ throw new Error("Native memory request was cancelled");
204
+ }
205
+ const handshake = this.ensureHandshake(process);
206
+ if (!signal) {
207
+ await handshake.promise;
208
+ return;
209
+ }
210
+ let onAbort;
211
+ const abortPromise = new Promise((_, reject) => {
212
+ if (signal.aborted) {
213
+ reject(new Error("Native memory request was cancelled"));
214
+ return;
215
+ }
216
+ onAbort = () => {
217
+ reject(new Error("Native memory request was cancelled"));
218
+ };
219
+ signal.addEventListener("abort", onAbort, { once: true });
220
+ });
221
+ try {
222
+ await Promise.race([handshake.promise, abortPromise]);
223
+ }
224
+ finally {
225
+ if (onAbort) {
226
+ signal.removeEventListener("abort", onAbort);
227
+ }
228
+ }
229
+ }
230
+ ensureHandshake(process) {
231
+ if (this.handshake?.generation === process.generation) {
232
+ return this.handshake;
233
+ }
234
+ const handshake = {
235
+ generation: process.generation,
236
+ promise: Promise.resolve(),
237
+ };
238
+ this.handshake = handshake;
239
+ handshake.promise = Promise.resolve().then(async () => {
240
+ const status = await this.sendRequest(process, "status", {}, undefined, INITIALIZATION_TIMEOUT_MS);
241
+ const protocolVer = status.rpc_protocol_version;
242
+ if (protocolVer !== SUPPORTED_RPC_VERSION) {
243
+ if (protocolVer === undefined || protocolVer === null) {
244
+ throw new Error(`Native memory backend does not report its RPC protocol version. ` +
245
+ `Run \`bun run build:native:release\` to rebuild the binary.`);
246
+ }
247
+ throw new Error(`Native memory RPC protocol version mismatch: ` +
248
+ `client supports ${SUPPORTED_RPC_VERSION}, backend reports ${protocolVer}. ` +
249
+ `Run \`bun run build:native:release\` to rebuild the binary.`);
250
+ }
251
+ });
252
+ return handshake;
253
+ }
254
+ // ---- Internal: process management ---------------------------------------
255
+ start() {
256
+ if (this.process && this.isCurrentAndRunning(this.process))
257
+ return this.process;
258
+ const binary = this.usingSpawnOverride
259
+ ? (process.env.OPENCODE_NATIVE_MEMORY_BIN ??
260
+ resolve(this.root, "target", "release", "opencode-memory"))
261
+ : resolveNativeMemoryBinary(this.root);
262
+ const child = this.spawnFn(binary, [], {
263
+ cwd: this.worktree,
264
+ detached: process.platform !== "win32",
265
+ env: {
266
+ ...process.env,
267
+ OPENCODE_MEMORY_PROJECT_ROOT: this.worktree,
268
+ },
269
+ stdio: ["pipe", "pipe", "pipe"],
270
+ });
271
+ this.generation += 1;
272
+ const gen = this.generation;
273
+ this.process = { child, generation: gen };
274
+ this.handshake = undefined;
275
+ const frameDecoder = new DelimitedFrameDecoder(MAX_RESPONSE_BYTES);
276
+ let stderr = "";
277
+ const processState = this.process;
278
+ child.stdout.on("data", (chunk) => {
279
+ try {
280
+ for (const frame of frameDecoder.push(chunk)) {
281
+ this.handleFrame(frame, gen, processState);
282
+ }
283
+ }
284
+ catch (error) {
285
+ const detail = error instanceof Error ? error.message : String(error);
286
+ this.failProcess(processState, new Error(detail));
287
+ }
288
+ });
289
+ child.stderr.on("data", (chunk) => {
290
+ stderr = `${stderr}${chunk.toString("utf8")}`.slice(-MAX_STDERR_BYTES);
291
+ });
292
+ child.stdin.on("error", (error) => {
293
+ this.failProcess(processState, new Error(`Native memory stdin failed: ${error.message}`));
294
+ });
295
+ child.once("error", (error) => {
296
+ const hint = error.code === "ENOENT" ? "Run `bun run memory:build:release`." : error.message;
297
+ this.failProcess(processState, new Error(`Native memory failed to start: ${hint}`));
298
+ });
299
+ child.once("exit", (code, signal) => {
300
+ if (this.process?.generation === gen)
301
+ this.process = undefined;
302
+ if (this.handshake?.generation === gen)
303
+ this.handshake = undefined;
304
+ this.rejectGeneration(gen, new ProcessReplacedError(`Native memory exited with ${code ?? signal ?? "unknown status"}`));
305
+ });
306
+ child.once("close", (code, signal) => {
307
+ if (this.process?.generation !== gen)
308
+ return;
309
+ this.process = undefined;
310
+ if (this.disposed && code === 0)
311
+ return;
312
+ const detail = stderr.trim();
313
+ this.rejectGeneration(gen, new Error(`Native memory exited with ${code ?? signal ?? "unknown status"}${detail ? `: ${detail}` : ""}`));
314
+ });
315
+ return processState;
316
+ }
317
+ // ---- Internal: Protobuf frame handling (generation-aware) ---------------
318
+ handleFrame(frame, gen, process) {
319
+ let response;
320
+ try {
321
+ response = decodeResponse(frame);
322
+ }
323
+ catch (error) {
324
+ const detail = error instanceof Error ? error.message : String(error);
325
+ this.failProcess(process, new Error(`Native memory returned invalid Protobuf: ${detail}`));
326
+ return;
327
+ }
328
+ // An id=0 error applies to the whole protocol generation.
329
+ if (response.id === 0) {
330
+ this.failProcess(process, new Error(response.error || "Native memory protocol error"));
331
+ return;
332
+ }
333
+ if (!Number.isSafeInteger(response.id) || typeof response.ok !== "boolean") {
334
+ this.failProcess(process, new Error("Native memory returned an invalid response"));
335
+ return;
336
+ }
337
+ const pending = this.pending.get(response.id);
338
+ // Only resolve/reject if the pending belongs to this generation
339
+ if (!pending || pending.generation !== gen)
340
+ return;
341
+ this.finishPending(response.id, pending);
342
+ if (response.ok)
343
+ pending.resolve(response.result);
344
+ else
345
+ pending.reject(new Error(response.error || "Native memory operation failed"));
346
+ }
347
+ finishPending(id, pending) {
348
+ this.pending.delete(id);
349
+ clearTimeout(pending.timer);
350
+ if (pending.signal && pending.abort) {
351
+ pending.signal.removeEventListener("abort", pending.abort);
352
+ }
353
+ }
354
+ rejectGeneration(gen, error) {
355
+ for (const [id, pending] of this.pending) {
356
+ if (pending.generation !== gen)
357
+ continue;
358
+ this.finishPending(id, pending);
359
+ pending.reject(error);
360
+ }
361
+ }
362
+ failProcess(process, error) {
363
+ if (this.process?.generation === process.generation)
364
+ this.process = undefined;
365
+ if (this.handshake?.generation === process.generation)
366
+ this.handshake = undefined;
367
+ this.rejectGeneration(process.generation, error);
368
+ stopProcessTree(process.child, "SIGTERM");
369
+ }
370
+ isCurrentAndRunning(process) {
371
+ return (this.process?.generation === process.generation &&
372
+ process.child.exitCode === null &&
373
+ process.child.signalCode === null);
374
+ }
375
+ }
376
+ function stopProcessTree(child, signal) {
377
+ if (!child.pid)
378
+ return;
379
+ if (child.exitCode !== null || child.signalCode !== null)
380
+ return;
381
+ if (process.platform !== "win32") {
382
+ try {
383
+ process.kill(-child.pid, signal);
384
+ return;
385
+ }
386
+ catch {
387
+ // Fall back to the direct child.
388
+ }
389
+ }
390
+ try {
391
+ child.kill(signal);
392
+ }
393
+ catch {
394
+ // Process already exited.
395
+ }
396
+ }
397
+ //# sourceMappingURL=sidecar-client.js.map