@memoraone/mcp 0.1.41 → 0.1.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/daemon.cjs CHANGED
@@ -1373,9 +1373,9 @@ function bindingRelevantValuesMatch(a, b) {
1373
1373
  const envB = b.environment ?? void 0;
1374
1374
  return a.repositoryBindingId === b.repositoryBindingId && a.projectId.trim().toLowerCase() === b.projectId.trim().toLowerCase() && path9.resolve(a.workspaceRoot) === path9.resolve(b.workspaceRoot) && (a.installationPublicId ?? void 0) === (b.installationPublicId ?? void 0) && envA === envB && a.status === b.status;
1375
1375
  }
1376
- async function reconcileResolvedBindingWithDisk(cached) {
1376
+ async function reconcileResolvedBindingWithDisk(cached, options = {}) {
1377
1377
  const repositoryBindingId = assertRepositoryBindingId(cached.repositoryBindingId);
1378
- const record = await readBindingRecord(repositoryBindingId);
1378
+ const record = await readBindingRecord(repositoryBindingId, options.homeDir);
1379
1379
  if (!record) {
1380
1380
  throw new ReconnectRequiredError(
1381
1381
  `[memoraone-mcp] Cached binding missing for ${repositoryBindingId}. Run: memoraone-mcp connect <code>`
@@ -1407,6 +1407,21 @@ async function reconcileResolvedBindingWithDisk(cached) {
1407
1407
  }
1408
1408
  return { binding: fresh, cacheRefreshed: true };
1409
1409
  }
1410
+ async function checkRuntimeBindingValidity(cached, options = {}) {
1411
+ try {
1412
+ const { binding: current } = await reconcileResolvedBindingWithDisk(cached, options);
1413
+ if (current.status !== "connected") {
1414
+ return { valid: false, reason: `binding status is ${current.status}` };
1415
+ }
1416
+ if (!bindingRelevantValuesMatch(cached, current)) {
1417
+ return { valid: false, reason: "binding metadata changed" };
1418
+ }
1419
+ return { valid: true };
1420
+ } catch (err) {
1421
+ const reason = err instanceof Error && err.message.trim() ? err.message : String(err);
1422
+ return { valid: false, reason };
1423
+ }
1424
+ }
1410
1425
  function encodeResolvedBinding(binding) {
1411
1426
  const payload = {
1412
1427
  repositoryBindingId: binding.repositoryBindingId,
@@ -2860,6 +2875,8 @@ function createDaemonHeartbeat(opts) {
2860
2875
  let announced = false;
2861
2876
  let studioActive = null;
2862
2877
  let starting = false;
2878
+ let tickInFlight = false;
2879
+ let bindingAuthorityEnded = false;
2863
2880
  let waitingForIdeType = false;
2864
2881
  const sessionId = opts.sessionId ?? generateMcpSessionId();
2865
2882
  const packageVersion = opts.packageVersion ?? resolvePackageVersion();
@@ -2875,6 +2892,29 @@ function createDaemonHeartbeat(opts) {
2875
2892
  process.stderr.write(`[memoraone-mcp][daemon-heartbeat] ${redactSensitiveText(msg)}
2876
2893
  `);
2877
2894
  });
2895
+ const validateBinding = opts.validateBinding ?? checkRuntimeBindingValidity;
2896
+ const endBindingAuthority = (reason) => {
2897
+ if (bindingAuthorityEnded) return;
2898
+ bindingAuthorityEnded = true;
2899
+ studioActive = false;
2900
+ runGeneration += 1;
2901
+ if (interval) {
2902
+ clearInterval(interval);
2903
+ interval = null;
2904
+ }
2905
+ log2(`heartbeat authority ended: ${redactSensitiveText(reason)}`);
2906
+ };
2907
+ const bindingIsValid = async () => {
2908
+ try {
2909
+ const result = await validateBinding(opts.binding);
2910
+ if (result.valid) return true;
2911
+ endBindingAuthority("reason" in result ? result.reason : "binding is no longer valid");
2912
+ return false;
2913
+ } catch (err) {
2914
+ endBindingAuthority(`binding validation failed: ${String(err)}`);
2915
+ return false;
2916
+ }
2917
+ };
2878
2918
  const ensureClient = async () => {
2879
2919
  if (client) return client;
2880
2920
  const creds = await readInstallationCredentials(opts.binding.repositoryBindingId);
@@ -2925,10 +2965,17 @@ function createDaemonHeartbeat(opts) {
2925
2965
  }
2926
2966
  };
2927
2967
  const tick = async (generation) => {
2928
- if (!client || generation !== runGeneration) return;
2929
- const outcome = await sendProjectHeartbeat(client, ctx);
2930
- if (generation !== runGeneration || !interval) return;
2931
- applyHeartbeatOutcome(outcome);
2968
+ if (!client || generation !== runGeneration || bindingAuthorityEnded || tickInFlight) return;
2969
+ tickInFlight = true;
2970
+ try {
2971
+ if (!await bindingIsValid()) return;
2972
+ if (generation !== runGeneration || !interval) return;
2973
+ const outcome = await sendProjectHeartbeat(client, ctx);
2974
+ if (generation !== runGeneration || !interval) return;
2975
+ applyHeartbeatOutcome(outcome);
2976
+ } finally {
2977
+ tickInFlight = false;
2978
+ }
2932
2979
  };
2933
2980
  const beginInterval = () => {
2934
2981
  if (interval) return;
@@ -2956,8 +3003,13 @@ function createDaemonHeartbeat(opts) {
2956
3003
  log2("already starting (skipped duplicate start)");
2957
3004
  return;
2958
3005
  }
3006
+ if (bindingAuthorityEnded) {
3007
+ log2("binding authority already ended; heartbeat will not restart");
3008
+ return;
3009
+ }
2959
3010
  starting = true;
2960
3011
  try {
3012
+ if (!await bindingIsValid()) return;
2961
3013
  const activeClient = await ensureClient();
2962
3014
  if (!activeClient) return;
2963
3015
  const ok = await ensureAnnounced(activeClient);
package/dist/index.cjs CHANGED
@@ -1452,9 +1452,9 @@ function bindingRelevantValuesMatch(a, b) {
1452
1452
  const envB = b.environment ?? void 0;
1453
1453
  return a.repositoryBindingId === b.repositoryBindingId && a.projectId.trim().toLowerCase() === b.projectId.trim().toLowerCase() && path10.resolve(a.workspaceRoot) === path10.resolve(b.workspaceRoot) && (a.installationPublicId ?? void 0) === (b.installationPublicId ?? void 0) && envA === envB && a.status === b.status;
1454
1454
  }
1455
- async function reconcileResolvedBindingWithDisk(cached) {
1455
+ async function reconcileResolvedBindingWithDisk(cached, options = {}) {
1456
1456
  const repositoryBindingId = assertRepositoryBindingId(cached.repositoryBindingId);
1457
- const record = await readBindingRecord(repositoryBindingId);
1457
+ const record = await readBindingRecord(repositoryBindingId, options.homeDir);
1458
1458
  if (!record) {
1459
1459
  throw new ReconnectRequiredError(
1460
1460
  `[memoraone-mcp] Cached binding missing for ${repositoryBindingId}. Run: memoraone-mcp connect <code>`
@@ -1486,6 +1486,21 @@ async function reconcileResolvedBindingWithDisk(cached) {
1486
1486
  }
1487
1487
  return { binding: fresh, cacheRefreshed: true };
1488
1488
  }
1489
+ async function checkRuntimeBindingValidity(cached, options = {}) {
1490
+ try {
1491
+ const { binding: current } = await reconcileResolvedBindingWithDisk(cached, options);
1492
+ if (current.status !== "connected") {
1493
+ return { valid: false, reason: `binding status is ${current.status}` };
1494
+ }
1495
+ if (!bindingRelevantValuesMatch(cached, current)) {
1496
+ return { valid: false, reason: "binding metadata changed" };
1497
+ }
1498
+ return { valid: true };
1499
+ } catch (err) {
1500
+ const reason = err instanceof Error && err.message.trim() ? err.message : String(err);
1501
+ return { valid: false, reason };
1502
+ }
1503
+ }
1489
1504
  function encodeResolvedBinding(binding) {
1490
1505
  const payload = {
1491
1506
  repositoryBindingId: binding.repositoryBindingId,
@@ -2788,6 +2803,8 @@ function createDaemonHeartbeat(opts) {
2788
2803
  let announced = false;
2789
2804
  let studioActive = null;
2790
2805
  let starting = false;
2806
+ let tickInFlight = false;
2807
+ let bindingAuthorityEnded = false;
2791
2808
  let waitingForIdeType = false;
2792
2809
  const sessionId = opts.sessionId ?? generateMcpSessionId();
2793
2810
  const packageVersion = opts.packageVersion ?? resolvePackageVersion();
@@ -2803,6 +2820,29 @@ function createDaemonHeartbeat(opts) {
2803
2820
  process.stderr.write(`[memoraone-mcp][daemon-heartbeat] ${redactSensitiveText(msg)}
2804
2821
  `);
2805
2822
  });
2823
+ const validateBinding = opts.validateBinding ?? checkRuntimeBindingValidity;
2824
+ const endBindingAuthority = (reason) => {
2825
+ if (bindingAuthorityEnded) return;
2826
+ bindingAuthorityEnded = true;
2827
+ studioActive = false;
2828
+ runGeneration += 1;
2829
+ if (interval) {
2830
+ clearInterval(interval);
2831
+ interval = null;
2832
+ }
2833
+ log(`heartbeat authority ended: ${redactSensitiveText(reason)}`);
2834
+ };
2835
+ const bindingIsValid = async () => {
2836
+ try {
2837
+ const result = await validateBinding(opts.binding);
2838
+ if (result.valid) return true;
2839
+ endBindingAuthority("reason" in result ? result.reason : "binding is no longer valid");
2840
+ return false;
2841
+ } catch (err) {
2842
+ endBindingAuthority(`binding validation failed: ${String(err)}`);
2843
+ return false;
2844
+ }
2845
+ };
2806
2846
  const ensureClient = async () => {
2807
2847
  if (client) return client;
2808
2848
  const creds = await readInstallationCredentials(opts.binding.repositoryBindingId);
@@ -2853,10 +2893,17 @@ function createDaemonHeartbeat(opts) {
2853
2893
  }
2854
2894
  };
2855
2895
  const tick = async (generation) => {
2856
- if (!client || generation !== runGeneration) return;
2857
- const outcome = await sendProjectHeartbeat(client, ctx);
2858
- if (generation !== runGeneration || !interval) return;
2859
- applyHeartbeatOutcome(outcome);
2896
+ if (!client || generation !== runGeneration || bindingAuthorityEnded || tickInFlight) return;
2897
+ tickInFlight = true;
2898
+ try {
2899
+ if (!await bindingIsValid()) return;
2900
+ if (generation !== runGeneration || !interval) return;
2901
+ const outcome = await sendProjectHeartbeat(client, ctx);
2902
+ if (generation !== runGeneration || !interval) return;
2903
+ applyHeartbeatOutcome(outcome);
2904
+ } finally {
2905
+ tickInFlight = false;
2906
+ }
2860
2907
  };
2861
2908
  const beginInterval = () => {
2862
2909
  if (interval) return;
@@ -2884,8 +2931,13 @@ function createDaemonHeartbeat(opts) {
2884
2931
  log("already starting (skipped duplicate start)");
2885
2932
  return;
2886
2933
  }
2934
+ if (bindingAuthorityEnded) {
2935
+ log("binding authority already ended; heartbeat will not restart");
2936
+ return;
2937
+ }
2887
2938
  starting = true;
2888
2939
  try {
2940
+ if (!await bindingIsValid()) return;
2889
2941
  const activeClient = await ensureClient();
2890
2942
  if (!activeClient) return;
2891
2943
  const ok = await ensureAnnounced(activeClient);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memoraone/mcp",
3
- "version": "0.1.41",
3
+ "version": "0.1.42",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "bin": {