@memoraone/mcp 0.1.29 → 0.1.31

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 (4) hide show
  1. package/dist/cli.cjs +2060 -515
  2. package/dist/daemon.cjs +426 -146
  3. package/dist/index.cjs +371 -120
  4. package/package.json +1 -1
package/dist/daemon.cjs CHANGED
@@ -32,15 +32,61 @@ __export(daemon_exports, {
32
32
  runDaemon: () => runDaemon
33
33
  });
34
34
  module.exports = __toCommonJS(daemon_exports);
35
- var fs6 = __toESM(require("fs"), 1);
35
+ var fs7 = __toESM(require("fs"), 1);
36
36
  var net = __toESM(require("net"), 1);
37
37
  var import_stdio2 = require("@modelcontextprotocol/sdk/server/stdio.js");
38
38
 
39
39
  // src/socketPaths.ts
40
40
  var os = __toESM(require("os"), 1);
41
- var path = __toESM(require("path"), 1);
41
+ var path2 = __toESM(require("path"), 1);
42
42
  var fs = __toESM(require("fs"), 1);
43
- var BASE_DIR = process.env.MEMORAONE_MCP_LOCK_DIR || path.join(os.homedir(), ".memoraone-mcp");
43
+
44
+ // src/bindingIdentity.ts
45
+ var crypto = __toESM(require("crypto"), 1);
46
+ var path = __toESM(require("path"), 1);
47
+ var BINDING_SOCKET_HASH_LENGTH = 16;
48
+ function hashBindingIdentity(projectId, workspaceRoot, ideType) {
49
+ const input = [
50
+ projectId.trim().toLowerCase(),
51
+ path.resolve(workspaceRoot),
52
+ ideType
53
+ ].join("|");
54
+ return crypto.createHash("sha256").update(input).digest("hex").slice(0, BINDING_SOCKET_HASH_LENGTH);
55
+ }
56
+ function bindingsMatch(a, b) {
57
+ return a.projectId.trim().toLowerCase() === b.projectId.trim().toLowerCase() && path.resolve(a.workspaceRoot) === path.resolve(b.workspaceRoot) && path.resolve(a.m1Path) === path.resolve(b.m1Path);
58
+ }
59
+ function formatMissingInitializeWorkspaceError(options) {
60
+ const lines = [
61
+ "[memoraone-mcp] Could not resolve workspace from MCP initialize params."
62
+ ];
63
+ if (options?.rootsListAttempted) {
64
+ lines.push(
65
+ "Cursor initialize lacked workspaceFolders/rootUri; roots/list was attempted but returned no usable repo root."
66
+ );
67
+ if (options.rootsListUris && options.rootsListUris.length > 0) {
68
+ lines.push(`roots/list URIs: ${options.rootsListUris.join(", ")}`);
69
+ }
70
+ lines.push(
71
+ "Global Cursor MCP cannot safely bind per-window repos without a workspace signal from Cursor (initialize roots or roots/list)."
72
+ );
73
+ lines.push(
74
+ "Reload MCP in this Cursor window, or ensure this repo has a managed .cursor/mcp.json from setup-ide-files --cursor."
75
+ );
76
+ return lines.join("\n");
77
+ }
78
+ lines.push(
79
+ "Reload MCP in this Cursor window so initialize includes workspaceFolders, rootUri, or a usable roots/list response for this repo."
80
+ );
81
+ return lines.join("\n");
82
+ }
83
+
84
+ // src/socketPaths.ts
85
+ var BASE_DIR = process.env.MEMORAONE_MCP_LOCK_DIR || path2.join(os.homedir(), ".memoraone-mcp");
86
+ var HASH_SOCKET_FILENAME_RE = new RegExp(
87
+ `^mcp-[0-9a-f]{${BINDING_SOCKET_HASH_LENGTH}}\\.sock$`,
88
+ "i"
89
+ );
44
90
  var IDE_TYPES = ["cursor", "copilot-vscode", "jetbrains"];
45
91
  var IDE_TYPE_SET = new Set(IDE_TYPES);
46
92
  function parseIdeType(value) {
@@ -59,11 +105,16 @@ function parseIdeTypeFromArgv(args) {
59
105
  }
60
106
  return parseIdeType(args[idx + 1]);
61
107
  }
62
- function getSocketPath(projectId, ideType) {
63
- if (ideType) {
64
- return path.join(BASE_DIR, `mcp-${projectId}-${ideType}.sock`);
65
- }
66
- return path.join(BASE_DIR, `mcp-${projectId}.sock`);
108
+ function resolveBindingIdeType(env2 = process.env) {
109
+ return resolveIdeTypeFromEnv(env2) ?? "";
110
+ }
111
+ function getBindingSocketFilename(binding, env2 = process.env) {
112
+ const ideType = resolveBindingIdeType(env2);
113
+ const hash = hashBindingIdentity(binding.projectId, binding.workspaceRoot, ideType);
114
+ return `mcp-${hash}.sock`;
115
+ }
116
+ function getBindingSocketPath(binding, env2 = process.env) {
117
+ return path2.join(BASE_DIR, getBindingSocketFilename(binding, env2));
67
118
  }
68
119
  function ensureBaseDir() {
69
120
  fs.mkdirSync(BASE_DIR, { recursive: true });
@@ -72,7 +123,7 @@ function ensureBaseDir() {
72
123
 
73
124
  // src/projectBinding.ts
74
125
  var fs2 = __toESM(require("fs/promises"), 1);
75
- var path2 = __toESM(require("path"), 1);
126
+ var path3 = __toESM(require("path"), 1);
76
127
  var uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
77
128
  function normalizeEnvironment(raw) {
78
129
  if (raw === void 0 || raw === null || typeof raw !== "string") {
@@ -105,7 +156,7 @@ async function resolveProjectIdFromExplicitM1Path() {
105
156
  if (raw === void 0 || raw.trim() === "") {
106
157
  return null;
107
158
  }
108
- const markerPath = path2.resolve(raw);
159
+ const markerPath = path3.resolve(raw);
109
160
  try {
110
161
  const content = await fs2.readFile(markerPath, "utf8");
111
162
  const { projectId, apiKey, environment } = parseAndValidateM1(content, markerPath);
@@ -118,20 +169,20 @@ async function resolveProjectIdFromExplicitM1Path() {
118
169
  }
119
170
  }
120
171
  async function findM1WalkingUp(workspaceRoot) {
121
- let current = path2.resolve(workspaceRoot);
172
+ let current = path3.resolve(workspaceRoot);
122
173
  while (true) {
123
- const markerPath = path2.join(current, "memoraone.m1");
174
+ const markerPath = path3.join(current, "memoraone.m1");
124
175
  try {
125
176
  const content = await fs2.readFile(markerPath, "utf8");
126
177
  const { projectId, apiKey, environment } = parseAndValidateM1(content, markerPath);
127
- const repoRoot = path2.dirname(markerPath);
178
+ const repoRoot = path3.dirname(markerPath);
128
179
  return environment === void 0 ? { projectId, apiKey, repoRoot, markerPath } : { projectId, apiKey, environment, repoRoot, markerPath };
129
180
  } catch (err) {
130
181
  if (err?.code !== "ENOENT") {
131
182
  throw err;
132
183
  }
133
184
  }
134
- const parent = path2.dirname(current);
185
+ const parent = path3.dirname(current);
135
186
  if (parent === current) {
136
187
  break;
137
188
  }
@@ -154,7 +205,7 @@ function normalizeWorkspaceSearchRoots(workspaceRoot) {
154
205
  if (trimmed === "") {
155
206
  continue;
156
207
  }
157
- const resolved = path2.resolve(trimmed);
208
+ const resolved = path3.resolve(trimmed);
158
209
  if (!seen.has(resolved)) {
159
210
  seen.add(resolved);
160
211
  out.push(resolved);
@@ -176,29 +227,33 @@ function resolveApiKeyWithSource(fileApiKey) {
176
227
  }
177
228
  return { apiKey: null, apiKeySource: "none" };
178
229
  }
179
- async function resolveAuthoritativeBinding(workspaceRoot) {
180
- const explicitBinding = await resolveProjectIdFromExplicitM1Path();
181
- if (explicitBinding) {
182
- const resolved = resolveApiKeyWithSource(explicitBinding.apiKey);
183
- return {
184
- projectId: explicitBinding.projectId,
185
- workspaceRoot: path2.dirname(explicitBinding.foundAt),
186
- m1Path: explicitBinding.foundAt,
187
- apiKey: resolved.apiKey,
188
- ...explicitBinding.environment !== void 0 ? { environment: explicitBinding.environment } : {},
189
- bindingSource: "explicit-m1-path",
190
- apiKeySource: resolved.apiKeySource
191
- };
230
+ async function resolveAuthoritativeBinding(workspaceRoot, options = {}) {
231
+ const respectExplicitM1Path = options.respectExplicitM1Path !== false;
232
+ if (respectExplicitM1Path) {
233
+ const explicitBinding = await resolveProjectIdFromExplicitM1Path();
234
+ if (explicitBinding) {
235
+ const resolved = resolveApiKeyWithSource(explicitBinding.apiKey);
236
+ return {
237
+ projectId: explicitBinding.projectId,
238
+ workspaceRoot: path3.dirname(explicitBinding.foundAt),
239
+ m1Path: explicitBinding.foundAt,
240
+ apiKey: resolved.apiKey,
241
+ ...explicitBinding.environment !== void 0 ? { environment: explicitBinding.environment } : {},
242
+ bindingSource: "explicit-m1-path",
243
+ apiKeySource: resolved.apiKeySource
244
+ };
245
+ }
192
246
  }
193
247
  const candidates = normalizeWorkspaceSearchRoots(workspaceRoot);
194
248
  if (candidates.length === 0) {
195
249
  throw new Error("Could not find memoraone.m1 in workspace.\nOpen a folder containing memoraone.m1.");
196
250
  }
251
+ const bindings = [];
197
252
  for (const root of candidates) {
198
253
  const binding = await findM1WalkingUp(root);
199
254
  if (binding) {
200
255
  const resolved = resolveApiKeyWithSource(binding.apiKey);
201
- return {
256
+ bindings.push({
202
257
  projectId: binding.projectId,
203
258
  workspaceRoot: binding.repoRoot,
204
259
  m1Path: binding.markerPath,
@@ -206,10 +261,25 @@ async function resolveAuthoritativeBinding(workspaceRoot) {
206
261
  ...binding.environment !== void 0 ? { environment: binding.environment } : {},
207
262
  bindingSource: "workspace-search",
208
263
  apiKeySource: resolved.apiKeySource
209
- };
264
+ });
210
265
  }
211
266
  }
212
- throw new Error("Could not find memoraone.m1 in workspace.\nOpen a folder containing memoraone.m1.");
267
+ if (bindings.length === 0) {
268
+ throw new Error("Could not find memoraone.m1 in workspace.\nOpen a folder containing memoraone.m1.");
269
+ }
270
+ const distinctProjectIds = new Set(bindings.map((b) => b.projectId));
271
+ if (distinctProjectIds.size > 1) {
272
+ const lines = bindings.map(
273
+ (b) => ` - workspace=${b.workspaceRoot} project=${b.projectId} m1=${b.m1Path}`
274
+ );
275
+ throw new Error(
276
+ "[memoraone-mcp] Ambiguous workspace binding: multiple open roots map to different MemoraOne projects.\n" + lines.join("\n") + "\nOpen one repo per Cursor window, or use repo-scoped .cursor/mcp.json from setup-ide-files --cursor."
277
+ );
278
+ }
279
+ return bindings[0];
280
+ }
281
+ function encodeResolvedBinding(binding) {
282
+ return Buffer.from(JSON.stringify(binding), "utf8").toString("base64");
213
283
  }
214
284
  function decodeResolvedBinding(value) {
215
285
  if (!value) {
@@ -257,17 +327,43 @@ function decodeResolvedBinding(value) {
257
327
  };
258
328
  }
259
329
 
330
+ // src/bindingSidecar.ts
331
+ var fs3 = __toESM(require("fs"), 1);
332
+ var path4 = __toESM(require("path"), 1);
333
+ function bindingSidecarPath(socketPath) {
334
+ if (socketPath.endsWith(".sock")) {
335
+ return `${socketPath.slice(0, -".sock".length)}.binding.json`;
336
+ }
337
+ return `${socketPath}.binding.json`;
338
+ }
339
+ function writeBindingSidecar(socketPath, binding, ideType = resolveBindingIdeType()) {
340
+ const payload = encodeResolvedBinding(binding);
341
+ const record = {
342
+ v: 2,
343
+ ...ideType ? { ideType } : {},
344
+ projectId: binding.projectId,
345
+ workspaceRoot: binding.workspaceRoot,
346
+ m1Path: binding.m1Path,
347
+ binding: payload
348
+ };
349
+ fs3.writeFileSync(bindingSidecarPath(socketPath), JSON.stringify(record), "utf8");
350
+ }
351
+ function removeBindingSidecar(socketPath) {
352
+ try {
353
+ fs3.unlinkSync(bindingSidecarPath(socketPath));
354
+ } catch {
355
+ }
356
+ }
357
+
260
358
  // src/index.ts
261
- var path7 = __toESM(require("path"), 1);
262
- var import_node_url2 = require("url");
263
359
  var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
264
360
  var import_types = require("@modelcontextprotocol/sdk/types.js");
265
361
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
266
362
 
267
363
  // src/config.ts
268
364
  var process2 = __toESM(require("process"), 1);
269
- var fs3 = __toESM(require("fs"), 1);
270
- var path3 = __toESM(require("path"), 1);
365
+ var fs4 = __toESM(require("fs"), 1);
366
+ var path5 = __toESM(require("path"), 1);
271
367
  var dotenv = __toESM(require("dotenv"), 1);
272
368
  var import_v4 = require("zod/v4");
273
369
 
@@ -290,8 +386,8 @@ function resolveApiUrl(env2) {
290
386
  }
291
387
 
292
388
  // src/config.ts
293
- var dotenvPath = path3.resolve(process2.cwd(), ".env");
294
- if (fs3.existsSync(dotenvPath)) {
389
+ var dotenvPath = path5.resolve(process2.cwd(), ".env");
390
+ if (fs4.existsSync(dotenvPath)) {
295
391
  try {
296
392
  dotenv.config({ path: dotenvPath });
297
393
  } catch (err) {
@@ -358,7 +454,7 @@ var config2 = {
358
454
  };
359
455
 
360
456
  // src/client/memoraClient.ts
361
- var crypto = __toESM(require("crypto"), 1);
457
+ var crypto2 = __toESM(require("crypto"), 1);
362
458
  var PROJECT_ID_HEADER = "x-project-id";
363
459
  var uuidRegex2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
364
460
  var parseBooleanFlag2 = (value) => {
@@ -427,12 +523,12 @@ var MemoraClient = class {
427
523
  ...options?.headers ?? {}
428
524
  };
429
525
  }
430
- async post(path8, body, options) {
526
+ async post(path10, body, options) {
431
527
  console.error(
432
- `[memoraone-mcp][info] MemoraClient.post ENTER path=${path8}`
528
+ `[memoraone-mcp][info] MemoraClient.post ENTER path=${path10}`
433
529
  );
434
- const nonce = crypto.randomBytes(8).toString("hex");
435
- const url = `${this.baseUrl}${path8.startsWith("/") ? path8 : `/${path8}`}`;
530
+ const nonce = crypto2.randomBytes(8).toString("hex");
531
+ const url = `${this.baseUrl}${path10.startsWith("/") ? path10 : `/${path10}`}`;
436
532
  this.resolveProjectId();
437
533
  console.error(
438
534
  `[memoraone-mcp][info] requestJson nonce=${nonce} stage=before_fetch method=POST url=${url}`
@@ -465,13 +561,13 @@ var MemoraClient = class {
465
561
  throw new MemoraOneHttpError(res.status, res.statusText, res.text);
466
562
  }
467
563
  console.error(
468
- `[memoraone-mcp][info] MemoraClient.post EXIT path=${path8}`
564
+ `[memoraone-mcp][info] MemoraClient.post EXIT path=${path10}`
469
565
  );
470
566
  return res.text ? JSON.parse(res.text) : null;
471
567
  }
472
- async get(path8, options) {
473
- const nonce = crypto.randomBytes(8).toString("hex");
474
- const url = `${this.baseUrl}${path8.startsWith("/") ? path8 : `/${path8}`}`;
568
+ async get(path10, options) {
569
+ const nonce = crypto2.randomBytes(8).toString("hex");
570
+ const url = `${this.baseUrl}${path10.startsWith("/") ? path10 : `/${path10}`}`;
475
571
  this.resolveProjectId();
476
572
  console.error(
477
573
  `[memoraone-mcp][info] requestJson nonce=${nonce} stage=before_fetch method=GET url=${url}`
@@ -501,15 +597,195 @@ var MemoraClient = class {
501
597
  };
502
598
  var memoraClient_default = MemoraClient;
503
599
 
504
- // src/sourceRegistration.ts
505
- var path4 = __toESM(require("path"), 1);
600
+ // src/initializeBinding.ts
601
+ var path6 = __toESM(require("path"), 1);
506
602
  var import_node_url = require("url");
603
+ var MEMORAONE_WORKSPACE_ROOT_ENV = "MEMORAONE_WORKSPACE_ROOT";
604
+ function getBridgeBindingResolveOptions(env2 = process.env) {
605
+ const ideType = resolveIdeTypeFromEnv(env2);
606
+ if (ideType === "cursor") {
607
+ return { respectExplicitM1Path: false, allowEnvWorkspaceFallback: false };
608
+ }
609
+ return { respectExplicitM1Path: true, allowEnvWorkspaceFallback: true };
610
+ }
611
+ function uriToPath(uri) {
612
+ if (uri.startsWith("file://")) {
613
+ return (0, import_node_url.fileURLToPath)(uri);
614
+ }
615
+ return uri;
616
+ }
617
+ function getEnvWorkspaceRootCandidates() {
618
+ const raw = process.env.WORKSPACE_FOLDER_PATHS;
619
+ const parts = [];
620
+ if (raw !== void 0 && raw.trim() !== "") {
621
+ for (const p of raw.split(path6.delimiter).map((s) => s.trim()).filter(Boolean)) {
622
+ parts.push(path6.resolve(p));
623
+ }
624
+ }
625
+ parts.push(process.cwd());
626
+ const seen = /* @__PURE__ */ new Set();
627
+ const deduped = [];
628
+ for (const p of parts) {
629
+ if (!seen.has(p)) {
630
+ seen.add(p);
631
+ deduped.push(p);
632
+ }
633
+ }
634
+ return deduped;
635
+ }
636
+ function extractWorkspaceRootsFromInitialize(params) {
637
+ if (!params) {
638
+ return [];
639
+ }
640
+ const seen = /* @__PURE__ */ new Set();
641
+ const roots = [];
642
+ const addRoot = (uri) => {
643
+ if (uri === void 0 || uri.trim() === "") {
644
+ return;
645
+ }
646
+ const resolved = path6.resolve(uriToPath(uri));
647
+ if (!seen.has(resolved)) {
648
+ seen.add(resolved);
649
+ roots.push(resolved);
650
+ }
651
+ };
652
+ if (Array.isArray(params.workspaceFolders) && params.workspaceFolders.length > 0) {
653
+ for (const folder of params.workspaceFolders) {
654
+ addRoot(folder?.uri);
655
+ }
656
+ return roots;
657
+ }
658
+ if (params.rootUri) {
659
+ addRoot(params.rootUri);
660
+ }
661
+ return roots;
662
+ }
663
+ function getRepoScopedWorkspaceHint(env2 = process.env) {
664
+ const raw = env2[MEMORAONE_WORKSPACE_ROOT_ENV];
665
+ if (raw === void 0 || raw.trim() === "") {
666
+ return null;
667
+ }
668
+ return path6.resolve(raw.trim());
669
+ }
670
+ function formatWorkspaceAmbiguityError(bindings) {
671
+ const lines = bindings.map(
672
+ (b) => ` - workspace=${b.workspaceRoot} project=${b.projectId} m1=${b.m1Path}`
673
+ );
674
+ return "[memoraone-mcp] Ambiguous workspace binding: multiple open roots map to different MemoraOne projects.\n" + lines.join("\n") + `
675
+ Open one repo per Cursor window, or ensure this repo's managed .cursor/mcp.json includes ${MEMORAONE_WORKSPACE_ROOT_ENV} from setup-ide-files --cursor.`;
676
+ }
677
+ function formatRepoHintInitializeMismatchError(repoHintRoot, initializeBinding) {
678
+ return `[memoraone-mcp] Repo-scoped workspace hint conflicts with MCP initialize workspace.
679
+ ${MEMORAONE_WORKSPACE_ROOT_ENV}: ${repoHintRoot}
680
+ initialize: project=${initializeBinding.projectId} workspace=${initializeBinding.workspaceRoot} m1=${initializeBinding.m1Path}
681
+ Reload MCP in the Cursor window for this repo, or re-run setup-ide-files --cursor here if the hint is stale.`;
682
+ }
683
+ function formatRepoHintNotInRootsListError(repoHintRoot, rootsListPaths) {
684
+ return `[memoraone-mcp] Repo-scoped workspace hint does not match any Cursor roots/list entry.
685
+ ${MEMORAONE_WORKSPACE_ROOT_ENV}: ${repoHintRoot}
686
+ roots/list paths: ${rootsListPaths.join(", ")}
687
+ Reload MCP in the Cursor window for this repo, or re-run setup-ide-files --cursor if the hint is stale.`;
688
+ }
689
+ function formatBindingMismatchError(daemonHint, sessionBinding) {
690
+ return `[memoraone-mcp] Project binding mismatch between daemon and MCP initialize workspace.
691
+ daemon: project=${daemonHint.projectId} workspace=${daemonHint.workspaceRoot} m1=${daemonHint.m1Path}
692
+ initialize: project=${sessionBinding.projectId} workspace=${sessionBinding.workspaceRoot} m1=${sessionBinding.m1Path}
693
+ Reconnect or reload the MCP server in this IDE window so the bridge can bind to the correct project.`;
694
+ }
695
+ async function resolveBindingFromWorkspaceRoots(workspaceRoots, options = {}) {
696
+ if (workspaceRoots.length === 0) {
697
+ throw new Error("Could not find memoraone.m1 in workspace.\nOpen a folder containing memoraone.m1.");
698
+ }
699
+ const bindings = [];
700
+ for (const root of workspaceRoots) {
701
+ try {
702
+ bindings.push(
703
+ await resolveAuthoritativeBinding(root, {
704
+ respectExplicitM1Path: options.respectExplicitM1Path
705
+ })
706
+ );
707
+ } catch (err) {
708
+ if (err instanceof Error && err.message.includes("Could not find memoraone.m1")) {
709
+ continue;
710
+ }
711
+ throw err;
712
+ }
713
+ }
714
+ if (bindings.length === 0) {
715
+ throw new Error("Could not find memoraone.m1 in workspace.\nOpen a folder containing memoraone.m1.");
716
+ }
717
+ const distinctProjectIds = new Set(bindings.map((b) => b.projectId));
718
+ if (distinctProjectIds.size > 1) {
719
+ throw new Error(formatWorkspaceAmbiguityError(bindings));
720
+ }
721
+ return bindings[0];
722
+ }
723
+ async function resolveBindingFromInitializeParams(params, options = {}) {
724
+ const env2 = options.env ?? process.env;
725
+ const resolveOpts = {
726
+ respectExplicitM1Path: options.respectExplicitM1Path,
727
+ allowEnvWorkspaceFallback: options.allowEnvWorkspaceFallback
728
+ };
729
+ const repoHint = getRepoScopedWorkspaceHint(env2);
730
+ const initializeRoots = extractWorkspaceRootsFromInitialize(params);
731
+ if (initializeRoots.length > 0) {
732
+ const binding = await resolveBindingFromWorkspaceRoots(initializeRoots, resolveOpts);
733
+ if (repoHint !== null) {
734
+ const hintBinding = await resolveAuthoritativeBinding(repoHint, {
735
+ respectExplicitM1Path: false
736
+ });
737
+ if (!bindingsMatch(binding, hintBinding)) {
738
+ throw new Error(formatRepoHintInitializeMismatchError(repoHint, binding));
739
+ }
740
+ }
741
+ return binding;
742
+ }
743
+ const rootsListUris = options.rootsListUris ?? [];
744
+ const rootsListPaths = rootsListUris.map((uri) => uriToPath(uri)).filter(Boolean);
745
+ if (rootsListPaths.length > 1 && repoHint !== null) {
746
+ const hintResolved = path6.resolve(repoHint);
747
+ const matchingRoot = rootsListPaths.find((root) => path6.resolve(root) === hintResolved);
748
+ if (!matchingRoot) {
749
+ throw new Error(formatRepoHintNotInRootsListError(repoHint, rootsListPaths));
750
+ }
751
+ return resolveAuthoritativeBinding(repoHint, { respectExplicitM1Path: false });
752
+ }
753
+ if (rootsListPaths.length > 0) {
754
+ return resolveBindingFromWorkspaceRoots(rootsListPaths, resolveOpts);
755
+ }
756
+ if (repoHint !== null) {
757
+ return resolveAuthoritativeBinding(repoHint, { respectExplicitM1Path: false });
758
+ }
759
+ if (options.allowEnvWorkspaceFallback === false) {
760
+ throw new Error(
761
+ formatMissingInitializeWorkspaceError({
762
+ rootsListAttempted: options.rootsListAttempted === true,
763
+ rootsListUris
764
+ })
765
+ );
766
+ }
767
+ const fallbackRoots = options.fallbackWorkspaceRoots ?? getEnvWorkspaceRootCandidates();
768
+ return resolveAuthoritativeBinding(fallbackRoots, {
769
+ respectExplicitM1Path: options.respectExplicitM1Path
770
+ });
771
+ }
772
+
773
+ // src/bridgeClientRoots.ts
774
+ var readline = __toESM(require("readline"), 1);
775
+ var TRUTHY = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
776
+ function isInitializeDebugEnabled(env2 = process.env) {
777
+ return TRUTHY.has(String(env2.MEMORAONE_DEBUG_INIT ?? "").trim().toLowerCase());
778
+ }
779
+
780
+ // src/sourceRegistration.ts
781
+ var path7 = __toESM(require("path"), 1);
782
+ var import_node_url2 = require("url");
507
783
  var LOG_PREFIX = "[memoraone-mcp][source-registration]";
508
784
  function buildRepoSourcePayload(normalizedRepoPath, ideType) {
509
785
  const body = {
510
786
  kind: "repo",
511
- label: path4.basename(normalizedRepoPath),
512
- uri: (0, import_node_url.pathToFileURL)(normalizedRepoPath).href
787
+ label: path7.basename(normalizedRepoPath),
788
+ uri: (0, import_node_url2.pathToFileURL)(normalizedRepoPath).href
513
789
  };
514
790
  if (ideType) {
515
791
  body.metadata = {
@@ -527,7 +803,7 @@ async function registerRepoSource(client, projectId, repoPath, ideType) {
527
803
  );
528
804
  return;
529
805
  }
530
- const normalizedRepoPath = path4.resolve(String(repoPath));
806
+ const normalizedRepoPath = path7.resolve(String(repoPath));
531
807
  const body = buildRepoSourcePayload(normalizedRepoPath, ideType);
532
808
  const primaryPath = `/v1/projects/${projectId}/sources`;
533
809
  const alternatePath = `/v1/projects/${projectId}/sources/register`;
@@ -687,11 +963,11 @@ var bindingStatusShape = {};
687
963
 
688
964
  // src/tools/handlers/postEvent.ts
689
965
  var import_v412 = require("zod/v4");
690
- var crypto3 = __toESM(require("crypto"), 1);
966
+ var crypto4 = __toESM(require("crypto"), 1);
691
967
 
692
968
  // src/runContext.ts
693
969
  var import_node_async_hooks = require("async_hooks");
694
- var crypto2 = __toESM(require("crypto"), 1);
970
+ var crypto3 = __toESM(require("crypto"), 1);
695
971
  var sessionContextStorage = new import_node_async_hooks.AsyncLocalStorage();
696
972
  function createSessionRunContext(initial = {}) {
697
973
  return {
@@ -744,7 +1020,7 @@ function resolveRunId(passed) {
744
1020
  return getCurrentRunId();
745
1021
  }
746
1022
  function generateRunId() {
747
- return crypto2.randomBytes(16).toString("hex");
1023
+ return crypto3.randomBytes(16).toString("hex");
748
1024
  }
749
1025
 
750
1026
  // src/tools/handlers/postEvent.ts
@@ -784,7 +1060,7 @@ function buildPostEventContentFields(content) {
784
1060
  return { message: JSON.stringify(content) };
785
1061
  }
786
1062
  async function handlePostEvent(client, args) {
787
- const nonce = crypto3.randomBytes(8).toString("hex");
1063
+ const nonce = crypto4.randomBytes(8).toString("hex");
788
1064
  console.error(
789
1065
  `[memoraone-mcp][debug] tool=memora_post_event toolCallId=unknown nonce=${nonce} stage=before_post`
790
1066
  );
@@ -939,9 +1215,9 @@ function buildPersonalContextPath(parsed2) {
939
1215
  }
940
1216
  async function handleGetPersonalContext(client, args) {
941
1217
  const parsed2 = getPersonalContextInputSchema.parse(args ?? {});
942
- const path8 = buildPersonalContextPath(parsed2);
1218
+ const path10 = buildPersonalContextPath(parsed2);
943
1219
  try {
944
- const result = await client.get(path8);
1220
+ const result = await client.get(path10);
945
1221
  return { ok: true, result };
946
1222
  } catch (err) {
947
1223
  if (err instanceof MemoraOneHttpError) {
@@ -1182,9 +1458,9 @@ async function handleListProjects(client) {
1182
1458
  var import_v421 = require("zod/v4");
1183
1459
 
1184
1460
  // src/repoFingerprint.ts
1185
- var fs4 = __toESM(require("fs"), 1);
1186
- var path5 = __toESM(require("path"), 1);
1187
- var crypto4 = __toESM(require("crypto"), 1);
1461
+ var fs5 = __toESM(require("fs"), 1);
1462
+ var path8 = __toESM(require("path"), 1);
1463
+ var crypto5 = __toESM(require("crypto"), 1);
1188
1464
  var parseBooleanFlag3 = (value) => {
1189
1465
  if (!value) {
1190
1466
  return false;
@@ -1209,20 +1485,20 @@ var normalizeRemoteUrl = (remoteUrl) => {
1209
1485
  return normalized.toLowerCase();
1210
1486
  };
1211
1487
  var sha256 = (value) => {
1212
- return crypto4.createHash("sha256").update(value).digest("hex");
1488
+ return crypto5.createHash("sha256").update(value).digest("hex");
1213
1489
  };
1214
1490
  var resolveGitDir = (gitPath) => {
1215
1491
  try {
1216
- const stat2 = fs4.statSync(gitPath);
1492
+ const stat2 = fs5.statSync(gitPath);
1217
1493
  if (stat2.isDirectory()) {
1218
1494
  return gitPath;
1219
1495
  }
1220
1496
  if (stat2.isFile()) {
1221
- const content = fs4.readFileSync(gitPath, "utf8");
1497
+ const content = fs5.readFileSync(gitPath, "utf8");
1222
1498
  const match = content.match(/^gitdir:\s*(.+)$/m);
1223
1499
  if (match) {
1224
1500
  const gitDir = match[1].trim();
1225
- return path5.resolve(path5.dirname(gitPath), gitDir);
1501
+ return path8.resolve(path8.dirname(gitPath), gitDir);
1226
1502
  }
1227
1503
  }
1228
1504
  } catch {
@@ -1231,16 +1507,16 @@ var resolveGitDir = (gitPath) => {
1231
1507
  return null;
1232
1508
  };
1233
1509
  var findGitRoot = (start) => {
1234
- let current = path5.resolve(start);
1510
+ let current = path8.resolve(start);
1235
1511
  while (true) {
1236
- const gitPath = path5.join(current, ".git");
1237
- if (fs4.existsSync(gitPath)) {
1512
+ const gitPath = path8.join(current, ".git");
1513
+ if (fs5.existsSync(gitPath)) {
1238
1514
  const gitDir = resolveGitDir(gitPath);
1239
1515
  if (gitDir) {
1240
1516
  return { gitRoot: current, gitDir };
1241
1517
  }
1242
1518
  }
1243
- const parent = path5.dirname(current);
1519
+ const parent = path8.dirname(current);
1244
1520
  if (parent === current) {
1245
1521
  break;
1246
1522
  }
@@ -1249,9 +1525,9 @@ var findGitRoot = (start) => {
1249
1525
  return null;
1250
1526
  };
1251
1527
  var readOriginRemote = (gitDir) => {
1252
- const configPath = path5.join(gitDir, "config");
1528
+ const configPath = path8.join(gitDir, "config");
1253
1529
  try {
1254
- const content = fs4.readFileSync(configPath, "utf8");
1530
+ const content = fs5.readFileSync(configPath, "utf8");
1255
1531
  const lines = content.split(/\r?\n/);
1256
1532
  let inOrigin = false;
1257
1533
  for (const line of lines) {
@@ -1275,7 +1551,7 @@ var readOriginRemote = (gitDir) => {
1275
1551
  function resolveRepoFingerprint(cwd2) {
1276
1552
  const found = findGitRoot(cwd2);
1277
1553
  if (!found) {
1278
- const fallbackPath = path5.resolve(cwd2);
1554
+ const fallbackPath = path8.resolve(cwd2);
1279
1555
  const fingerprint2 = sha256(fallbackPath);
1280
1556
  debugLog(`repo fingerprint=${fingerprint2} source=path-fallback`);
1281
1557
  return {
@@ -1297,7 +1573,7 @@ function resolveRepoFingerprint(cwd2) {
1297
1573
  source: "git-remote"
1298
1574
  };
1299
1575
  }
1300
- const fingerprint = sha256(path5.resolve(gitRoot));
1576
+ const fingerprint = sha256(path8.resolve(gitRoot));
1301
1577
  debugLog(`repo fingerprint=${fingerprint} source=path-fallback`);
1302
1578
  return {
1303
1579
  fingerprint,
@@ -1307,8 +1583,8 @@ function resolveRepoFingerprint(cwd2) {
1307
1583
  }
1308
1584
 
1309
1585
  // src/workspaceMap.ts
1310
- var fs5 = __toESM(require("fs/promises"), 1);
1311
- var path6 = __toESM(require("path"), 1);
1586
+ var fs6 = __toESM(require("fs/promises"), 1);
1587
+ var path9 = __toESM(require("path"), 1);
1312
1588
  var import_node_os = __toESM(require("os"), 1);
1313
1589
  var parseBooleanFlag4 = (value) => {
1314
1590
  if (!value) {
@@ -1327,11 +1603,11 @@ var debugLog2 = (message) => {
1327
1603
  };
1328
1604
  var fingerprintRegex = /^[0-9a-f]{64}$/i;
1329
1605
  function getWorkspaceMapPath() {
1330
- return path6.join(import_node_os.default.homedir(), ".memoraone", "workspaces.json");
1606
+ return path9.join(import_node_os.default.homedir(), ".memoraone", "workspaces.json");
1331
1607
  }
1332
1608
  var ensureWorkspaceDir = async () => {
1333
- const dir = path6.dirname(getWorkspaceMapPath());
1334
- await fs5.mkdir(dir, { recursive: true });
1609
+ const dir = path9.dirname(getWorkspaceMapPath());
1610
+ await fs6.mkdir(dir, { recursive: true });
1335
1611
  };
1336
1612
  async function acquireWorkspaceMapLock() {
1337
1613
  const filePath = getWorkspaceMapPath();
@@ -1345,10 +1621,10 @@ async function acquireWorkspaceMapLock() {
1345
1621
  while (!lockAcquired && retries < maxRetries) {
1346
1622
  try {
1347
1623
  try {
1348
- const stat2 = await fs5.stat(lockPath);
1624
+ const stat2 = await fs6.stat(lockPath);
1349
1625
  const ageMs = Date.now() - stat2.mtimeMs;
1350
1626
  if (ageMs > maxLockAgeMs) {
1351
- await fs5.unlink(lockPath);
1627
+ await fs6.unlink(lockPath);
1352
1628
  debugLog2(`removed stale workspace map lock (age: ${ageMs}ms)`);
1353
1629
  }
1354
1630
  } catch (err) {
@@ -1356,14 +1632,14 @@ async function acquireWorkspaceMapLock() {
1356
1632
  throw err;
1357
1633
  }
1358
1634
  }
1359
- const fd = await fs5.open(lockPath, "wx");
1635
+ const fd = await fs6.open(lockPath, "wx");
1360
1636
  await fd.close();
1361
1637
  lockAcquired = true;
1362
1638
  } catch (err) {
1363
1639
  if (err?.code === "EEXIST") {
1364
1640
  retries++;
1365
1641
  if (retries < maxRetries) {
1366
- await new Promise((resolve6) => setTimeout(resolve6, retryDelayMs));
1642
+ await new Promise((resolve7) => setTimeout(resolve7, retryDelayMs));
1367
1643
  continue;
1368
1644
  }
1369
1645
  throw new Error(
@@ -1375,7 +1651,7 @@ async function acquireWorkspaceMapLock() {
1375
1651
  }
1376
1652
  return async () => {
1377
1653
  try {
1378
- await fs5.unlink(lockPath);
1654
+ await fs6.unlink(lockPath);
1379
1655
  } catch (err) {
1380
1656
  if (err?.code !== "ENOENT") {
1381
1657
  debugLog2(`failed to release workspace map lock: ${String(err)}`);
@@ -1431,7 +1707,7 @@ var validateWorkspaceMap = (map, filePath) => {
1431
1707
  async function readWorkspaceMap() {
1432
1708
  const filePath = getWorkspaceMapPath();
1433
1709
  try {
1434
- const content = await fs5.readFile(filePath, "utf8");
1710
+ const content = await fs6.readFile(filePath, "utf8");
1435
1711
  const parsed2 = JSON.parse(content);
1436
1712
  validateWorkspaceMap(parsed2, filePath);
1437
1713
  const typed = parsed2;
@@ -1476,8 +1752,8 @@ async function writeWorkspaceMap(map) {
1476
1752
  await ensureWorkspaceDir();
1477
1753
  const tempPath = `${filePath}.tmp`;
1478
1754
  const content = JSON.stringify(map, null, 2);
1479
- await fs5.writeFile(tempPath, content, "utf8");
1480
- await fs5.rename(tempPath, filePath);
1755
+ await fs6.writeFile(tempPath, content, "utf8");
1756
+ await fs6.rename(tempPath, filePath);
1481
1757
  }
1482
1758
  async function setProjectIdForFingerprint(args) {
1483
1759
  const { fingerprint, projectKey, source, linked_at } = args;
@@ -1557,9 +1833,9 @@ function handleBindingStatus(binding) {
1557
1833
  }
1558
1834
 
1559
1835
  // src/heartbeat.ts
1560
- var crypto5 = __toESM(require("crypto"), 1);
1836
+ var crypto6 = __toESM(require("crypto"), 1);
1561
1837
  function fingerprintApiKey(apiKey) {
1562
- return crypto5.createHash("sha256").update(apiKey).digest("hex").slice(0, 12);
1838
+ return crypto6.createHash("sha256").update(apiKey).digest("hex").slice(0, 12);
1563
1839
  }
1564
1840
  function isHeartbeatDebugEnabled() {
1565
1841
  const value = String(process.env.MEMORAONE_DEBUG_HEARTBEAT ?? "").trim().toLowerCase();
@@ -1709,21 +1985,6 @@ var notInitializedResult = {
1709
1985
  ]
1710
1986
  };
1711
1987
  var initializeDiagDumped = false;
1712
- var uriToPath = (uri) => {
1713
- if (uri.startsWith("file://")) {
1714
- return (0, import_node_url2.fileURLToPath)(uri);
1715
- }
1716
- return uri;
1717
- };
1718
- function getCursorWorkspaceRootFromEnv() {
1719
- const raw = process.env.WORKSPACE_FOLDER_PATHS;
1720
- if (raw === void 0 || raw.trim() === "") {
1721
- return void 0;
1722
- }
1723
- const parts = raw.split(path7.delimiter).map((p) => p.trim()).filter(Boolean);
1724
- const first = parts[0];
1725
- return first ? path7.resolve(first) : void 0;
1726
- }
1727
1988
  function redactSensitiveFields(obj) {
1728
1989
  if (obj === null || obj === void 0) return obj;
1729
1990
  if (typeof obj !== "object") return obj;
@@ -1802,8 +2063,8 @@ function registerToolWithWorklog(server, runtime, sessionContext, toolName, desc
1802
2063
  async function main(opts = {}) {
1803
2064
  let bindingReadyResolve = null;
1804
2065
  let bindingReadyReject = null;
1805
- const bindingReady = new Promise((resolve6, reject) => {
1806
- bindingReadyResolve = resolve6;
2066
+ const bindingReady = new Promise((resolve7, reject) => {
2067
+ bindingReadyResolve = resolve7;
1807
2068
  bindingReadyReject = reject;
1808
2069
  });
1809
2070
  const devMode = Boolean(config2.devMode);
@@ -1814,7 +2075,7 @@ async function main(opts = {}) {
1814
2075
  projectId: null,
1815
2076
  apiKeySource: null,
1816
2077
  apiKeyFingerprint: null,
1817
- authoritativeBinding: opts.authoritativeBinding ?? null,
2078
+ authoritativeBinding: null,
1818
2079
  ideType: void 0
1819
2080
  };
1820
2081
  let workspaceRoot;
@@ -1822,42 +2083,53 @@ async function main(opts = {}) {
1822
2083
  name: "memoraone-mcp",
1823
2084
  version: "1.0.0"
1824
2085
  });
1825
- const resolveFallbackBindingFromInitialize = async (params) => {
1826
- let fallbackWorkspaceRoot = workspaceRoot;
2086
+ const resolveSessionBindingFromInitialize = async (params) => {
2087
+ const initializeRoots = extractWorkspaceRootsFromInitialize(params);
2088
+ if (initializeRoots.length === 0 && opts.daemonBindingHint) {
2089
+ if (isInitializeDebugEnabled()) {
2090
+ console.error(
2091
+ "[memoraone-mcp][init-debug] Workspace resolution strategy: daemonBindingHint (bridge pre-resolved)"
2092
+ );
2093
+ }
2094
+ return opts.daemonBindingHint;
2095
+ }
2096
+ let rootsListUris;
2097
+ let rootsListAttempted = false;
1827
2098
  try {
1828
2099
  const rootsResult = await server.server.listRoots({});
2100
+ rootsListAttempted = true;
1829
2101
  const roots = Array.isArray(rootsResult?.roots) ? rootsResult.roots : [];
1830
- console.error(`[memoraone-mcp] roots/list returned ${roots.length}: ${JSON.stringify(roots)}`);
1831
- if (!fallbackWorkspaceRoot && roots.length > 0 && roots[0]?.uri) {
1832
- fallbackWorkspaceRoot = uriToPath(roots[0].uri);
1833
- console.error("[memoraone-mcp] Workspace resolution strategy: roots/list (first root)");
2102
+ if (isInitializeDebugEnabled()) {
2103
+ console.error(`[memoraone-mcp][init-debug] roots/list returned ${roots.length}: ${JSON.stringify(roots)}`);
2104
+ }
2105
+ if (roots.length > 0) {
2106
+ rootsListUris = roots.map((root) => root?.uri).filter((uri) => Boolean(uri));
2107
+ if (rootsListUris.length > 0 && isInitializeDebugEnabled()) {
2108
+ console.error("[memoraone-mcp][init-debug] Workspace resolution strategy: roots/list");
2109
+ }
1834
2110
  }
1835
2111
  } catch (e) {
1836
- console.error("[memoraone-mcp] roots/list failed:", String(e));
2112
+ if (isInitializeDebugEnabled()) {
2113
+ console.error("[memoraone-mcp][init-debug] roots/list failed:", String(e));
2114
+ }
1837
2115
  }
1838
- console.error(`[memoraone-mcp] process.cwd(): ${process.cwd()}`);
1839
- console.error(
1840
- `[memoraone-mcp] WORKSPACE_FOLDER_PATHS: ${process.env.WORKSPACE_FOLDER_PATHS ?? "(unset)"}`
1841
- );
1842
- if (!fallbackWorkspaceRoot) {
2116
+ if (isInitializeDebugEnabled()) {
2117
+ console.error(`[memoraone-mcp][init-debug] process.cwd(): ${process.cwd()}`);
2118
+ console.error(
2119
+ `[memoraone-mcp][init-debug] WORKSPACE_FOLDER_PATHS: ${process.env.WORKSPACE_FOLDER_PATHS ?? "(unset)"}`
2120
+ );
1843
2121
  if (params.workspaceFolders?.length) {
1844
- fallbackWorkspaceRoot = uriToPath(params.workspaceFolders[0].uri);
1845
- console.error("[memoraone-mcp] Workspace resolution strategy: initialize.workspaceFolders");
2122
+ console.error("[memoraone-mcp][init-debug] Workspace resolution strategy: initialize.workspaceFolders");
1846
2123
  } else if (params.rootUri) {
1847
- fallbackWorkspaceRoot = uriToPath(params.rootUri);
1848
- console.error("[memoraone-mcp] Workspace resolution strategy: initialize.rootUri");
1849
- } else {
1850
- const cursorRoot = getCursorWorkspaceRootFromEnv();
1851
- if (cursorRoot !== void 0) {
1852
- fallbackWorkspaceRoot = cursorRoot;
1853
- console.error("[memoraone-mcp] Workspace resolution strategy: env.WORKSPACE_FOLDER_PATHS");
1854
- } else {
1855
- fallbackWorkspaceRoot = process.cwd();
1856
- console.error("[memoraone-mcp] Workspace resolution strategy: process.cwd() fallback");
1857
- }
2124
+ console.error("[memoraone-mcp][init-debug] Workspace resolution strategy: initialize.rootUri");
1858
2125
  }
1859
2126
  }
1860
- const binding = await resolveAuthoritativeBinding(fallbackWorkspaceRoot);
2127
+ const binding = await resolveBindingFromInitializeParams(params, {
2128
+ fallbackWorkspaceRoots: getEnvWorkspaceRootCandidates(),
2129
+ rootsListUris,
2130
+ rootsListAttempted,
2131
+ ...getBridgeBindingResolveOptions()
2132
+ });
1861
2133
  workspaceRoot = binding.workspaceRoot;
1862
2134
  return binding;
1863
2135
  };
@@ -2037,7 +2309,7 @@ async function main(opts = {}) {
2037
2309
  if (runtime.ideType && opts.onSessionIdeTypeKnown) {
2038
2310
  opts.onSessionIdeTypeKnown(runtime.ideType);
2039
2311
  }
2040
- if (!initializeDiagDumped) {
2312
+ if (isInitializeDebugEnabled() && !initializeDiagDumped) {
2041
2313
  initializeDiagDumped = true;
2042
2314
  const folders = Array.isArray(params.workspaceFolders) ? params.workspaceFolders.map((f) => ({ name: f?.name, uri: f?.uri })) : params.workspaceFolders;
2043
2315
  const capKeys = params.capabilities && typeof params.capabilities === "object" ? Object.keys(params.capabilities) : [];
@@ -2069,7 +2341,13 @@ async function main(opts = {}) {
2069
2341
  String(process.env.MEMORAONE_DEBUG_AUTH ?? "").trim().toLowerCase()
2070
2342
  );
2071
2343
  const debugLog3 = config2.devMode || debugAuth;
2072
- const binding = opts.authoritativeBinding ? opts.authoritativeBinding : await resolveFallbackBindingFromInitialize(params);
2344
+ const binding = await resolveSessionBindingFromInitialize(params);
2345
+ if (opts.daemonBindingHint && !bindingsMatch(opts.daemonBindingHint, binding)) {
2346
+ const errMsg = formatBindingMismatchError(opts.daemonBindingHint, binding);
2347
+ console.error(`[memoraone-mcp][ERROR] ${errMsg}`);
2348
+ bindingReadyReject?.(new Error(errMsg));
2349
+ throw new Error(errMsg);
2350
+ }
2073
2351
  const apiKeyToUse = binding.apiKey;
2074
2352
  if (!apiKeyToUse) {
2075
2353
  throw new Error(
@@ -2193,10 +2471,10 @@ async function main(opts = {}) {
2193
2471
  console.error("[memoraone-mcp] MCP server ready");
2194
2472
  }
2195
2473
  if (opts.sessionSocket) {
2196
- await new Promise((resolve6) => {
2474
+ await new Promise((resolve7) => {
2197
2475
  opts.sessionSocket.once("close", () => {
2198
2476
  shutdown("session closed", false);
2199
- resolve6();
2477
+ resolve7();
2200
2478
  });
2201
2479
  });
2202
2480
  }
@@ -2231,11 +2509,11 @@ function parseBindingFromEnv(projectId) {
2231
2509
  }
2232
2510
  async function ensureSocketClean(socketPath) {
2233
2511
  try {
2234
- fs6.accessSync(socketPath);
2512
+ fs7.accessSync(socketPath);
2235
2513
  } catch {
2236
2514
  return;
2237
2515
  }
2238
- return new Promise((resolve6) => {
2516
+ return new Promise((resolve7) => {
2239
2517
  const client = net.createConnection({ path: socketPath }, () => {
2240
2518
  client.destroy();
2241
2519
  log("daemon already running, exiting");
@@ -2243,11 +2521,11 @@ async function ensureSocketClean(socketPath) {
2243
2521
  });
2244
2522
  client.on("error", () => {
2245
2523
  try {
2246
- fs6.unlinkSync(socketPath);
2524
+ fs7.unlinkSync(socketPath);
2247
2525
  log("stale socket removed");
2248
2526
  } catch {
2249
2527
  }
2250
- resolve6();
2528
+ resolve7();
2251
2529
  });
2252
2530
  });
2253
2531
  }
@@ -2255,7 +2533,7 @@ async function runDaemon() {
2255
2533
  const projectId = parseProjectIdFromArgv();
2256
2534
  const binding = parseBindingFromEnv(projectId);
2257
2535
  const ideType = parseIdeTypeFromArgv(process.argv.slice(2)) ?? config2.ideType ?? resolveIdeTypeFromEnv();
2258
- const socketPath = getSocketPath(projectId, ideType);
2536
+ const socketPath = getBindingSocketPath(binding, process.env);
2259
2537
  let nextSessionId = 1;
2260
2538
  let activeSessions = 0;
2261
2539
  let idleTimer = null;
@@ -2263,7 +2541,7 @@ async function runDaemon() {
2263
2541
  const dir = ensureBaseDir();
2264
2542
  log(`directory ensured: ${dir}`);
2265
2543
  log(
2266
- `authoritative binding project=${binding.projectId} workspace=${binding.workspaceRoot} m1=${binding.m1Path} source=${binding.bindingSource} apiKeySource=${binding.apiKeySource}`
2544
+ `daemon spawn hint project=${binding.projectId} workspace=${binding.workspaceRoot} m1=${binding.m1Path} source=${binding.bindingSource} apiKeySource=${binding.apiKeySource}`
2267
2545
  );
2268
2546
  log("session policy: concurrent bridge sessions allowed per project daemon");
2269
2547
  if (!isDaemonIdleShutdownAllowed()) {
@@ -2277,10 +2555,11 @@ async function runDaemon() {
2277
2555
  await ensureSocketClean(socketPath);
2278
2556
  const cleanupSocketFile = () => {
2279
2557
  try {
2280
- if (fs6.existsSync(socketPath)) {
2281
- fs6.unlinkSync(socketPath);
2558
+ if (fs7.existsSync(socketPath)) {
2559
+ fs7.unlinkSync(socketPath);
2282
2560
  log("socket removed");
2283
2561
  }
2562
+ removeBindingSidecar(socketPath);
2284
2563
  } catch (err) {
2285
2564
  log(`socket cleanup warning: ${String(err)}`);
2286
2565
  }
@@ -2323,7 +2602,7 @@ async function runDaemon() {
2323
2602
  const transport = new import_stdio2.StdioServerTransport(socket, socket);
2324
2603
  try {
2325
2604
  await main({
2326
- authoritativeBinding: binding,
2605
+ daemonBindingHint: binding,
2327
2606
  transport,
2328
2607
  sessionSocket: socket,
2329
2608
  sessionLabel: `daemon-session-${sessionId}`,
@@ -2362,13 +2641,14 @@ async function runDaemon() {
2362
2641
  process.on("SIGINT", () => shutdownNow("SIGINT"));
2363
2642
  process.on("SIGTERM", () => shutdownNow("SIGTERM"));
2364
2643
  process.on("exit", cleanupSocketFile);
2365
- return new Promise((resolve6) => {
2644
+ return new Promise((resolve7) => {
2366
2645
  server.listen(socketPath, () => {
2646
+ writeBindingSidecar(socketPath, binding, ideType ?? "");
2367
2647
  log(`daemon started, listening on ${socketPath}`);
2368
2648
  void daemonHeartbeat.start().catch((err) => {
2369
2649
  log(`daemon heartbeat start error: ${String(err)}`);
2370
2650
  });
2371
- resolve6();
2651
+ resolve7();
2372
2652
  });
2373
2653
  });
2374
2654
  }