@zokizuan/satori-mcp 6.2.0 → 6.3.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.
@@ -0,0 +1,322 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import net from "node:net";
4
+ import { createMcpConfig, resolveMcpRuntimeBootstrap, } from "../config.js";
5
+ import { SHARED_RUNTIME_HANDSHAKE_MAX_BYTES, SHARED_RUNTIME_IDLE_MS, SHARED_RUNTIME_PROTOCOL_VERSION, buildSharedRuntimeIdentity, isSharedOfflineRuntimeEligible, resolveSharedRuntimePaths, } from "./shared-runtime-identity.js";
6
+ import { acquireLifecycleLock, readHostMetadata, readLinuxProcessIdentity, removeOwnedLifecycleState, writeHostMetadataAtomic, } from "./shared-runtime-lifecycle.js";
7
+ import { SharedRuntimeHost } from "./shared-runtime.js";
8
+ import { BoundedSocketTransport } from "./shared-runtime-transport.js";
9
+ function parseAttachRequest(line) {
10
+ try {
11
+ const value = JSON.parse(line);
12
+ if (value.type !== "satori-shared-runtime-attach"
13
+ || typeof value.protocolVersion !== "number"
14
+ || typeof value.sharedRuntimeIdentityHash !== "string"
15
+ || typeof value.installedRuntimeRoot !== "string"
16
+ || typeof value.mcpVersion !== "string"
17
+ || typeof value.launcherNonce !== "string"
18
+ || !/^[a-f0-9]{48}$/.test(value.launcherNonce)) {
19
+ return null;
20
+ }
21
+ return Object.freeze(value);
22
+ }
23
+ catch {
24
+ return null;
25
+ }
26
+ }
27
+ export class SharedRuntimeSocketHost {
28
+ constructor(runtimeHost, identity, paths, idleMs = SHARED_RUNTIME_IDLE_MS) {
29
+ this.runtimeHost = runtimeHost;
30
+ this.identity = identity;
31
+ this.paths = paths;
32
+ this.idleMs = idleMs;
33
+ this.sessions = new Set();
34
+ this.ownershipToken = crypto.randomUUID();
35
+ this.metadata = null;
36
+ this.idleTimer = null;
37
+ this.closing = false;
38
+ this.server = net.createServer({ pauseOnConnect: true }, (socket) => {
39
+ this.cancelIdleShutdown();
40
+ this.acceptHandshake(socket);
41
+ });
42
+ this.unsubscribeActivity = this.runtimeHost.subscribeActivity(() => {
43
+ const activity = this.runtimeHost.getActivity();
44
+ if (activity.sessions > 0 || activity.operations > 0) {
45
+ this.cancelIdleShutdown();
46
+ }
47
+ else {
48
+ this.scheduleIdleShutdown();
49
+ }
50
+ });
51
+ }
52
+ async start() {
53
+ let boundSocket = null;
54
+ try {
55
+ await this.runtimeHost.recoverInterruptedIndexingAtStartup();
56
+ await new Promise((resolve, reject) => {
57
+ const onError = (error) => {
58
+ this.server.off("listening", onListening);
59
+ reject(error);
60
+ };
61
+ const onListening = () => {
62
+ this.server.off("error", onError);
63
+ resolve();
64
+ };
65
+ this.server.once("error", onError);
66
+ this.server.once("listening", onListening);
67
+ this.server.listen(this.paths.socketPath);
68
+ });
69
+ const socketStat = fs.lstatSync(this.paths.socketPath);
70
+ boundSocket = Object.freeze({
71
+ device: socketStat.dev,
72
+ inode: socketStat.ino,
73
+ });
74
+ fs.chmodSync(this.paths.socketPath, 0o600);
75
+ const processIdentity = readLinuxProcessIdentity();
76
+ if (!processIdentity) {
77
+ throw new Error("Cannot establish shared runtime host process identity.");
78
+ }
79
+ this.metadata = Object.freeze({
80
+ formatVersion: 1,
81
+ protocolVersion: SHARED_RUNTIME_PROTOCOL_VERSION,
82
+ hostPid: processIdentity.pid,
83
+ bootId: processIdentity.bootId,
84
+ processStartTime: processIdentity.startTime,
85
+ mcpVersion: this.identity.mcpVersion,
86
+ sharedRuntimeIdentityHash: this.identity.hash,
87
+ installedRuntimeRoot: this.identity.installedRuntimeRoot,
88
+ ownershipToken: this.ownershipToken,
89
+ socketPath: this.paths.socketPath,
90
+ socketDevice: socketStat.dev,
91
+ socketInode: socketStat.ino,
92
+ readyAt: new Date().toISOString(),
93
+ });
94
+ writeHostMetadataAtomic(this.paths.metadataPath, this.metadata);
95
+ try {
96
+ fs.unlinkSync(this.paths.errorPath);
97
+ }
98
+ catch (error) {
99
+ if (error.code !== "ENOENT")
100
+ throw error;
101
+ }
102
+ this.scheduleIdleShutdown();
103
+ }
104
+ catch (error) {
105
+ this.closing = true;
106
+ if (this.server.listening) {
107
+ await new Promise((resolve) => {
108
+ this.server.close(() => resolve());
109
+ });
110
+ }
111
+ if (boundSocket) {
112
+ try {
113
+ const current = fs.lstatSync(this.paths.socketPath);
114
+ if (current.isSocket()
115
+ && current.dev === boundSocket.device
116
+ && current.ino === boundSocket.inode) {
117
+ fs.unlinkSync(this.paths.socketPath);
118
+ }
119
+ }
120
+ catch {
121
+ // The launcher owns stale-path recovery. Startup cleanup
122
+ // must still release provider and runtime-owner state.
123
+ }
124
+ }
125
+ this.unsubscribeActivity();
126
+ await this.runtimeHost.shutdown().catch(() => undefined);
127
+ throw error;
128
+ }
129
+ }
130
+ acceptHandshake(socket) {
131
+ let buffered = Buffer.alloc(0);
132
+ const timer = setTimeout(() => {
133
+ cleanup();
134
+ socket.destroy();
135
+ this.scheduleIdleShutdown();
136
+ }, 10000);
137
+ const cleanup = () => {
138
+ clearTimeout(timer);
139
+ socket.off("data", onData);
140
+ socket.off("error", onError);
141
+ socket.off("close", onClose);
142
+ };
143
+ const onError = () => {
144
+ cleanup();
145
+ this.scheduleIdleShutdown();
146
+ };
147
+ const onClose = () => {
148
+ cleanup();
149
+ this.scheduleIdleShutdown();
150
+ };
151
+ const reject = (message) => {
152
+ const response = {
153
+ type: "satori-shared-runtime-attached",
154
+ accepted: false,
155
+ protocolVersion: SHARED_RUNTIME_PROTOCOL_VERSION,
156
+ sharedRuntimeIdentityHash: this.identity.hash,
157
+ installedRuntimeRoot: this.identity.installedRuntimeRoot,
158
+ mcpVersion: this.identity.mcpVersion,
159
+ hostPid: process.pid,
160
+ bootId: this.metadata?.bootId ?? "",
161
+ processStartTime: this.metadata?.processStartTime ?? "",
162
+ launcherNonce: "",
163
+ error: message,
164
+ };
165
+ socket.once("error", () => {
166
+ // A rejected peer owns no session state.
167
+ });
168
+ socket.end(`${JSON.stringify(response)}\n`, () => socket.destroy());
169
+ this.scheduleIdleShutdown();
170
+ };
171
+ const onData = (chunk) => {
172
+ if (buffered.length + chunk.length > SHARED_RUNTIME_HANDSHAKE_MAX_BYTES) {
173
+ cleanup();
174
+ reject("Attach handshake exceeded its byte limit.");
175
+ return;
176
+ }
177
+ buffered = buffered.length === 0
178
+ ? Buffer.from(chunk)
179
+ : Buffer.concat([buffered, chunk]);
180
+ const newline = buffered.indexOf(0x0a);
181
+ if (newline < 0)
182
+ return;
183
+ cleanup();
184
+ const request = parseAttachRequest(buffered.subarray(0, newline).toString("utf8"));
185
+ const trailing = buffered.subarray(newline + 1);
186
+ if (!request) {
187
+ reject("Attach handshake is malformed.");
188
+ return;
189
+ }
190
+ if (request.protocolVersion !== SHARED_RUNTIME_PROTOCOL_VERSION
191
+ || request.sharedRuntimeIdentityHash !== this.identity.hash
192
+ || request.installedRuntimeRoot !== this.identity.installedRuntimeRoot
193
+ || request.mcpVersion !== this.identity.mcpVersion) {
194
+ reject("Attach handshake runtime identity does not match this host.");
195
+ return;
196
+ }
197
+ if (!this.metadata || this.closing) {
198
+ reject("Shared runtime host is not accepting sessions.");
199
+ return;
200
+ }
201
+ const response = {
202
+ type: "satori-shared-runtime-attached",
203
+ accepted: true,
204
+ protocolVersion: SHARED_RUNTIME_PROTOCOL_VERSION,
205
+ sharedRuntimeIdentityHash: this.identity.hash,
206
+ installedRuntimeRoot: this.identity.installedRuntimeRoot,
207
+ mcpVersion: this.identity.mcpVersion,
208
+ hostPid: this.metadata.hostPid,
209
+ bootId: this.metadata.bootId,
210
+ processStartTime: this.metadata.processStartTime,
211
+ launcherNonce: request.launcherNonce,
212
+ };
213
+ socket.write(`${JSON.stringify(response)}\n`);
214
+ void this.attachSession(socket, trailing).catch(() => {
215
+ socket.destroy();
216
+ this.scheduleIdleShutdown();
217
+ });
218
+ };
219
+ socket.on("data", onData);
220
+ socket.once("error", onError);
221
+ socket.once("close", onClose);
222
+ socket.resume();
223
+ }
224
+ async attachSession(socket, trailing) {
225
+ if (this.closing) {
226
+ socket.destroy();
227
+ return;
228
+ }
229
+ const session = this.runtimeHost.createSession();
230
+ let released = false;
231
+ const release = () => {
232
+ if (released)
233
+ return;
234
+ released = true;
235
+ this.sessions.delete(session);
236
+ void session.shutdown().finally(() => this.scheduleIdleShutdown());
237
+ };
238
+ const transport = new BoundedSocketTransport(socket, release, trailing);
239
+ this.sessions.add(session);
240
+ try {
241
+ await session.connect(transport);
242
+ }
243
+ catch (error) {
244
+ this.sessions.delete(session);
245
+ socket.destroy();
246
+ await session.shutdown();
247
+ this.scheduleIdleShutdown();
248
+ throw error;
249
+ }
250
+ }
251
+ cancelIdleShutdown() {
252
+ if (!this.idleTimer)
253
+ return;
254
+ clearTimeout(this.idleTimer);
255
+ this.idleTimer = null;
256
+ }
257
+ scheduleIdleShutdown() {
258
+ if (this.closing || this.sessions.size > 0 || this.idleTimer)
259
+ return;
260
+ const activity = this.runtimeHost.getActivity();
261
+ if (activity.sessions > 0 || activity.operations > 0)
262
+ return;
263
+ this.idleTimer = setTimeout(() => {
264
+ this.idleTimer = null;
265
+ void this.shutdown();
266
+ }, this.idleMs);
267
+ this.idleTimer.unref?.();
268
+ }
269
+ async shutdown() {
270
+ if (this.closing)
271
+ return;
272
+ this.closing = true;
273
+ this.cancelIdleShutdown();
274
+ const lock = await acquireLifecycleLock(this.paths.lockPath);
275
+ try {
276
+ if (this.runtimeHost.getActivity().operations > 0) {
277
+ this.closing = false;
278
+ this.scheduleIdleShutdown();
279
+ return;
280
+ }
281
+ await new Promise((resolve) => {
282
+ this.server.close(() => resolve());
283
+ });
284
+ await Promise.all([...this.sessions].map((session) => session.shutdown()));
285
+ this.sessions.clear();
286
+ await this.runtimeHost.shutdown();
287
+ this.unsubscribeActivity();
288
+ if (this.metadata) {
289
+ removeOwnedLifecycleState(this.paths, this.metadata);
290
+ }
291
+ }
292
+ finally {
293
+ lock.release();
294
+ }
295
+ }
296
+ }
297
+ export async function startSharedRuntimeHostFromEnv() {
298
+ const runtimeEntry = process.env.SATORI_SHARED_RUNTIME_ENTRY
299
+ ?? process.argv[1];
300
+ if (!runtimeEntry) {
301
+ throw new Error("Shared runtime host requires its installed runtime entry path.");
302
+ }
303
+ if (!isSharedOfflineRuntimeEligible(process.env)) {
304
+ throw new Error("Shared runtime host requires managed offline Potion + LanceDB configuration.");
305
+ }
306
+ const identity = buildSharedRuntimeIdentity(runtimeEntry, process.env);
307
+ if (process.env.SATORI_SHARED_RUNTIME_EXPECTED_HASH !== identity.hash) {
308
+ throw new Error("Shared runtime host identity differs from the starting launcher.");
309
+ }
310
+ const paths = resolveSharedRuntimePaths(identity, process.env);
311
+ const existing = readHostMetadata(paths.metadataPath);
312
+ if (existing) {
313
+ throw new Error("Shared runtime host metadata already exists; launcher recovery must resolve it.");
314
+ }
315
+ const parsedConfig = createMcpConfig();
316
+ const { config, runtimeFingerprint } = await resolveMcpRuntimeBootstrap(parsedConfig);
317
+ const runtimeHost = new SharedRuntimeHost(config, runtimeFingerprint, "host");
318
+ const socketHost = new SharedRuntimeSocketHost(runtimeHost, identity, paths);
319
+ await socketHost.start();
320
+ return socketHost;
321
+ }
322
+ //# sourceMappingURL=shared-runtime-host.js.map
@@ -0,0 +1,51 @@
1
+ export declare const SHARED_RUNTIME_PROTOCOL_VERSION = 1;
2
+ export declare const SHARED_RUNTIME_HANDSHAKE_MAX_BYTES: number;
3
+ export declare const SHARED_RUNTIME_MESSAGE_MAX_BYTES: number;
4
+ export declare const SHARED_RUNTIME_MAX_PENDING_REQUESTS = 16;
5
+ export declare const SHARED_RUNTIME_ATTACH_TIMEOUT_MS = 10000;
6
+ export declare const SHARED_RUNTIME_IDLE_MS = 60000;
7
+ export type SharedRuntimeIdentity = Readonly<{
8
+ protocolVersion: number;
9
+ mcpVersion: string;
10
+ installedRuntimeRoot: string;
11
+ stateRoot: string;
12
+ serverName: string;
13
+ serverVersion: string;
14
+ runtimeProfile: string;
15
+ embeddingProvider: string;
16
+ embeddingModel: string;
17
+ embeddingDimension: string;
18
+ hybridMode: string;
19
+ embeddingBatchSize: string;
20
+ potionHelperPath: string;
21
+ potionModelPath: string;
22
+ potionRequestTimeoutMs: string;
23
+ vectorStoreProvider: string;
24
+ lanceDbPath: string;
25
+ watcherEnabled: string;
26
+ watcherDebounceMs: string;
27
+ readFileMaxLines: string;
28
+ customExtensions: string;
29
+ customIgnorePatterns: string;
30
+ navigationBackend: string;
31
+ navigationDualRead: string;
32
+ allTextMaxBytes: string;
33
+ syncHashConcurrency: string;
34
+ syncFullHashEveryN: string;
35
+ sourceMeasurementLedger: string;
36
+ sourceMeasurementRoot: string;
37
+ hash: string;
38
+ }>;
39
+ export type SharedRuntimePaths = Readonly<{
40
+ stateRoot: string;
41
+ metadataDirectory: string;
42
+ metadataPath: string;
43
+ errorPath: string;
44
+ lockPath: string;
45
+ socketDirectory: string;
46
+ socketPath: string;
47
+ }>;
48
+ export declare function isSharedOfflineRuntimeEligible(env: NodeJS.ProcessEnv, platform?: NodeJS.Platform, architecture?: NodeJS.Architecture): boolean;
49
+ export declare function buildSharedRuntimeIdentity(runtimeEntry: string, env: NodeJS.ProcessEnv): SharedRuntimeIdentity;
50
+ export declare function resolveSharedRuntimePaths(identity: SharedRuntimeIdentity, env: NodeJS.ProcessEnv): SharedRuntimePaths;
51
+ //# sourceMappingURL=shared-runtime-identity.d.ts.map
@@ -0,0 +1,160 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ export const SHARED_RUNTIME_PROTOCOL_VERSION = 1;
6
+ export const SHARED_RUNTIME_HANDSHAKE_MAX_BYTES = 16 * 1024;
7
+ export const SHARED_RUNTIME_MESSAGE_MAX_BYTES = 8 * 1024 * 1024;
8
+ export const SHARED_RUNTIME_MAX_PENDING_REQUESTS = 16;
9
+ export const SHARED_RUNTIME_ATTACH_TIMEOUT_MS = 10000;
10
+ export const SHARED_RUNTIME_IDLE_MS = 60000;
11
+ function stableStringify(value) {
12
+ return `{${Object.keys(value)
13
+ .sort()
14
+ .map((key) => `${JSON.stringify(key)}:${JSON.stringify(value[key])}`)
15
+ .join(",")}}`;
16
+ }
17
+ function canonicalizePath(candidatePath) {
18
+ const absolutePath = path.resolve(candidatePath);
19
+ let existingPath = absolutePath;
20
+ while (!fs.existsSync(existingPath)) {
21
+ const parent = path.dirname(existingPath);
22
+ if (parent === existingPath) {
23
+ return absolutePath;
24
+ }
25
+ existingPath = parent;
26
+ }
27
+ const canonicalExistingPath = fs.realpathSync(existingPath);
28
+ return path.resolve(canonicalExistingPath, path.relative(existingPath, absolutePath));
29
+ }
30
+ function readPackageVersion(runtimeEntry) {
31
+ let current = path.dirname(path.resolve(runtimeEntry));
32
+ for (;;) {
33
+ const packagePath = path.join(current, "package.json");
34
+ if (fs.existsSync(packagePath)) {
35
+ const parsed = JSON.parse(fs.readFileSync(packagePath, "utf8"));
36
+ if (parsed.name === "@zokizuan/satori-mcp"
37
+ && typeof parsed.version === "string"
38
+ && parsed.version.length > 0) {
39
+ return {
40
+ version: parsed.version,
41
+ packageRoot: fs.realpathSync(current),
42
+ };
43
+ }
44
+ }
45
+ const parent = path.dirname(current);
46
+ if (parent === current)
47
+ break;
48
+ current = parent;
49
+ }
50
+ throw new Error(`Cannot locate @zokizuan/satori-mcp package for '${runtimeEntry}'.`);
51
+ }
52
+ export function isSharedOfflineRuntimeEligible(env, platform = process.platform, architecture = process.arch) {
53
+ return env.SATORI_SHARED_RUNTIME_DISABLE !== "1"
54
+ && env.SATORI_RUNTIME_PROFILE === "offline"
55
+ && env.EMBEDDING_PROVIDER === "Potion"
56
+ && env.VECTOR_STORE_PROVIDER === "LanceDB"
57
+ && platform === "linux"
58
+ && architecture === "x64";
59
+ }
60
+ export function buildSharedRuntimeIdentity(runtimeEntry, env) {
61
+ const installed = readPackageVersion(runtimeEntry);
62
+ const stateRoot = canonicalizePath(env.SATORI_STATE_ROOT
63
+ ?? path.join(env.HOME ?? os.homedir(), ".satori"));
64
+ const payload = {
65
+ protocolVersion: SHARED_RUNTIME_PROTOCOL_VERSION,
66
+ mcpVersion: installed.version,
67
+ installedRuntimeRoot: installed.packageRoot,
68
+ stateRoot,
69
+ serverName: env.MCP_SERVER_NAME ?? "",
70
+ serverVersion: env.MCP_SERVER_VERSION ?? "",
71
+ runtimeProfile: env.SATORI_RUNTIME_PROFILE ?? "",
72
+ embeddingProvider: env.EMBEDDING_PROVIDER ?? "",
73
+ embeddingModel: env.EMBEDDING_MODEL ?? "",
74
+ embeddingDimension: env.EMBEDDING_OUTPUT_DIMENSION ?? "",
75
+ hybridMode: env.HYBRID_MODE ?? "",
76
+ embeddingBatchSize: env.EMBEDDING_BATCH_SIZE ?? "",
77
+ potionHelperPath: env.POTION_HELPER_PATH
78
+ ? canonicalizePath(env.POTION_HELPER_PATH)
79
+ : "",
80
+ potionModelPath: env.POTION_MODEL_PATH
81
+ ? canonicalizePath(env.POTION_MODEL_PATH)
82
+ : "",
83
+ potionRequestTimeoutMs: env.POTION_REQUEST_TIMEOUT_MS ?? "",
84
+ vectorStoreProvider: env.VECTOR_STORE_PROVIDER ?? "",
85
+ lanceDbPath: env.LANCEDB_PATH
86
+ ? canonicalizePath(env.LANCEDB_PATH)
87
+ : "",
88
+ watcherEnabled: env.MCP_ENABLE_WATCHER ?? "",
89
+ watcherDebounceMs: env.MCP_WATCH_DEBOUNCE_MS ?? "",
90
+ readFileMaxLines: env.READ_FILE_MAX_LINES ?? "",
91
+ customExtensions: env.CUSTOM_EXTENSIONS ?? "",
92
+ customIgnorePatterns: env.CUSTOM_IGNORE_PATTERNS ?? "",
93
+ navigationBackend: env.SATORI_NAVIGATION_BACKEND ?? "",
94
+ navigationDualRead: env.SATORI_NAVIGATION_DUAL_READ ?? "",
95
+ allTextMaxBytes: env.SATORI_ALL_TEXT_MAX_BYTES ?? "",
96
+ syncHashConcurrency: env.SATORI_SYNC_HASH_CONCURRENCY ?? "",
97
+ syncFullHashEveryN: env.SATORI_SYNC_FULL_HASH_EVERY_N ?? "",
98
+ sourceMeasurementLedger: env.SATORI_SOURCE_MEASUREMENT_LEDGER ?? "",
99
+ sourceMeasurementRoot: env.SATORI_SOURCE_MEASUREMENT_ROOT ?? "",
100
+ };
101
+ return Object.freeze({
102
+ ...payload,
103
+ hash: crypto.createHash("sha256").update(stableStringify(payload)).digest("hex"),
104
+ });
105
+ }
106
+ function assertOwnedDirectory(directory) {
107
+ const stat = fs.lstatSync(directory);
108
+ if (!stat.isDirectory() || stat.isSymbolicLink()) {
109
+ throw new Error(`Shared runtime directory '${directory}' is not an owned directory.`);
110
+ }
111
+ if (typeof process.getuid === "function" && stat.uid !== process.getuid()) {
112
+ throw new Error(`Shared runtime directory '${directory}' belongs to another user.`);
113
+ }
114
+ const unsafeMode = stat.mode & 0o077;
115
+ if (unsafeMode !== 0) {
116
+ fs.chmodSync(directory, 0o700);
117
+ }
118
+ }
119
+ function ensureOwnedDirectory(directory) {
120
+ fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
121
+ assertOwnedDirectory(directory);
122
+ }
123
+ function resolveSocketDirectory(env, stateRoot) {
124
+ const configured = env.XDG_RUNTIME_DIR;
125
+ if (configured && path.isAbsolute(configured) && fs.existsSync(configured)) {
126
+ assertOwnedDirectory(configured);
127
+ const directory = path.join(configured, "satori");
128
+ ensureOwnedDirectory(directory);
129
+ return directory;
130
+ }
131
+ const uid = typeof process.getuid === "function" ? process.getuid() : process.pid;
132
+ const base = path.join(os.tmpdir(), `satori-${uid}`);
133
+ ensureOwnedDirectory(base);
134
+ const directory = path.join(base, "runtime");
135
+ ensureOwnedDirectory(directory);
136
+ // The state root is intentionally not used for the socket. A managed home
137
+ // can exceed Linux's small sockaddr_un path limit.
138
+ void stateRoot;
139
+ return directory;
140
+ }
141
+ export function resolveSharedRuntimePaths(identity, env) {
142
+ const stateRoot = identity.stateRoot;
143
+ const metadataDirectory = path.join(stateRoot, "runtime-host", identity.hash);
144
+ ensureOwnedDirectory(metadataDirectory);
145
+ const socketDirectory = resolveSocketDirectory(env, stateRoot);
146
+ const socketPath = path.join(socketDirectory, `${identity.hash.slice(0, 24)}.sock`);
147
+ if (Buffer.byteLength(socketPath, "utf8") > 100) {
148
+ throw new Error(`Shared runtime socket path is too long: '${socketPath}'.`);
149
+ }
150
+ return Object.freeze({
151
+ stateRoot,
152
+ metadataDirectory,
153
+ metadataPath: path.join(metadataDirectory, "host.json"),
154
+ errorPath: path.join(metadataDirectory, "startup-error.json"),
155
+ lockPath: path.join(metadataDirectory, "startup.lock"),
156
+ socketDirectory,
157
+ socketPath,
158
+ });
159
+ }
160
+ //# sourceMappingURL=shared-runtime-identity.js.map
@@ -0,0 +1,33 @@
1
+ import type { SharedRuntimeIdentity, SharedRuntimePaths } from "./shared-runtime-identity.js";
2
+ export type LinuxProcessIdentity = Readonly<{
3
+ pid: number;
4
+ bootId: string;
5
+ startTime: string;
6
+ }>;
7
+ export type SharedRuntimeHostMetadata = Readonly<{
8
+ formatVersion: 1;
9
+ protocolVersion: number;
10
+ hostPid: number;
11
+ bootId: string;
12
+ processStartTime: string;
13
+ mcpVersion: string;
14
+ sharedRuntimeIdentityHash: string;
15
+ installedRuntimeRoot: string;
16
+ ownershipToken: string;
17
+ socketPath: string;
18
+ socketDevice: number;
19
+ socketInode: number;
20
+ readyAt: string;
21
+ }>;
22
+ export type LifecycleLock = Readonly<{
23
+ ownershipToken: string;
24
+ release(): void;
25
+ }>;
26
+ export declare function readLinuxProcessIdentity(pid?: number): LinuxProcessIdentity | null;
27
+ export declare function isLinuxProcessIdentityLive(identity: Pick<LinuxProcessIdentity, "pid" | "bootId" | "startTime">): boolean;
28
+ export declare function readHostMetadata(metadataPath: string): SharedRuntimeHostMetadata | null;
29
+ export declare function writeHostMetadataAtomic(metadataPath: string, metadata: SharedRuntimeHostMetadata): void;
30
+ export declare function acquireLifecycleLock(lockPath: string, timeoutMs?: number): Promise<LifecycleLock>;
31
+ export declare function metadataMatchesIdentity(metadata: SharedRuntimeHostMetadata, identity: SharedRuntimeIdentity): boolean;
32
+ export declare function removeOwnedLifecycleState(paths: SharedRuntimePaths, expected: Pick<SharedRuntimeHostMetadata, "hostPid" | "bootId" | "processStartTime" | "ownershipToken" | "sharedRuntimeIdentityHash" | "socketPath" | "socketDevice" | "socketInode">): void;
33
+ //# sourceMappingURL=shared-runtime-lifecycle.d.ts.map