@memoraone/mcp 0.1.41 → 0.1.43

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/index.cjs CHANGED
@@ -593,7 +593,7 @@ async function acquireLocalLock(lockName, options = {}) {
593
593
  `[memoraone-mcp] Failed to acquire lock ${lockName} after ${maxRetries} retries`
594
594
  );
595
595
  }
596
- await new Promise((resolve9) => setTimeout(resolve9, retryDelayMs));
596
+ await new Promise((resolve10) => setTimeout(resolve10, retryDelayMs));
597
597
  continue;
598
598
  }
599
599
  throw err;
@@ -611,9 +611,10 @@ async function withLocalLock(lockName, fn, options = {}) {
611
611
  }
612
612
 
613
613
  // src/localState/localConnectClient.ts
614
- async function requestJson(baseUrl, method, path14, options = {}) {
614
+ var path6 = __toESM(require("path"), 1);
615
+ async function requestJson(baseUrl, method, path15, options = {}) {
615
616
  const fetchImpl = options.fetchImpl ?? fetch;
616
- const url = `${baseUrl.replace(/\/+$/, "")}${path14.startsWith("/") ? path14 : `/${path14}`}`;
617
+ const url = `${baseUrl.replace(/\/+$/, "")}${path15.startsWith("/") ? path15 : `/${path15}`}`;
617
618
  const res = await fetchImpl(url, {
618
619
  method,
619
620
  headers: {
@@ -719,7 +720,7 @@ async function refreshInstallationAccessToken(options) {
719
720
 
720
721
  // src/localState/bindingStore.ts
721
722
  var fs5 = __toESM(require("fs/promises"), 1);
722
- var path6 = __toESM(require("path"), 1);
723
+ var path7 = __toESM(require("path"), 1);
723
724
 
724
725
  // src/localState/bindingRecord.ts
725
726
  var BINDING_RECORD_VERSION = 1;
@@ -949,9 +950,9 @@ var MemoraClient = class {
949
950
  ...options?.headers ?? {}
950
951
  };
951
952
  }
952
- async perform(method, path14, body, options, retried = false) {
953
+ async perform(method, path15, body, options, retried = false) {
953
954
  const nonce = crypto2.randomBytes(8).toString("hex");
954
- const url = `${this.baseUrl}${path14.startsWith("/") ? path14 : `/${path14}`}`;
955
+ const url = `${this.baseUrl}${path15.startsWith("/") ? path15 : `/${path15}`}`;
955
956
  this.resolveProjectId();
956
957
  console.error(
957
958
  `[memoraone-mcp][info] requestJson nonce=${nonce} stage=before_fetch method=${method} url=${url}`
@@ -975,10 +976,10 @@ var MemoraClient = class {
975
976
  if (accepted) {
976
977
  return res.text ? JSON.parse(res.text) : null;
977
978
  }
978
- if ((res.status === 401 || res.status === 403) && !options?.skipAuthRefresh && !retried) {
979
+ if (res.status === 401 && !options?.skipAuthRefresh && !retried) {
979
980
  try {
980
981
  await this.refreshAccessToken();
981
- return this.perform(method, path14, body, options, true);
982
+ return this.perform(method, path15, body, options, true);
982
983
  } catch (err) {
983
984
  if (err instanceof ReconnectRequiredError) {
984
985
  await this.markReconnectRequired();
@@ -986,9 +987,6 @@ var MemoraClient = class {
986
987
  throw err;
987
988
  }
988
989
  }
989
- if (res.status === 401 || res.status === 403) {
990
- await this.markReconnectRequired();
991
- }
992
990
  const quiet = options?.quietHttpStatuses?.includes(res.status);
993
991
  if (!quiet) {
994
992
  const snippet = res.text.length > 200 ? `${res.text.slice(0, 200)}...` : res.text;
@@ -1001,33 +999,33 @@ var MemoraClient = class {
1001
999
  }
1002
1000
  return res.text ? JSON.parse(res.text) : null;
1003
1001
  }
1004
- async post(path14, body, options) {
1005
- console.error(`[memoraone-mcp][info] MemoraClient.post ENTER path=${path14}`);
1006
- const result = await this.perform("POST", path14, body, options);
1007
- console.error(`[memoraone-mcp][info] MemoraClient.post EXIT path=${path14}`);
1002
+ async post(path15, body, options) {
1003
+ console.error(`[memoraone-mcp][info] MemoraClient.post ENTER path=${path15}`);
1004
+ const result = await this.perform("POST", path15, body, options);
1005
+ console.error(`[memoraone-mcp][info] MemoraClient.post EXIT path=${path15}`);
1008
1006
  return result;
1009
1007
  }
1010
- async get(path14, options) {
1011
- return this.perform("GET", path14, void 0, options);
1008
+ async get(path15, options) {
1009
+ return this.perform("GET", path15, void 0, options);
1012
1010
  }
1013
1011
  };
1014
1012
  var memoraClient_default = MemoraClient;
1015
1013
 
1016
1014
  // src/projectBinding.ts
1017
1015
  var fs8 = __toESM(require("fs/promises"), 1);
1018
- var path10 = __toESM(require("path"), 1);
1016
+ var path11 = __toESM(require("path"), 1);
1019
1017
 
1020
1018
  // src/localState/resolveLocalBinding.ts
1021
1019
  var fs7 = __toESM(require("fs/promises"), 1);
1022
- var path9 = __toESM(require("path"), 1);
1020
+ var path10 = __toESM(require("path"), 1);
1023
1021
 
1024
1022
  // src/localState/pathIndex.ts
1025
- var path8 = __toESM(require("path"), 1);
1023
+ var path9 = __toESM(require("path"), 1);
1026
1024
 
1027
1025
  // src/localState/rootFilesystemIdentity.ts
1028
1026
  var fs6 = __toESM(require("fs/promises"), 1);
1029
1027
  var os3 = __toESM(require("os"), 1);
1030
- var path7 = __toESM(require("path"), 1);
1028
+ var path8 = __toESM(require("path"), 1);
1031
1029
  var import_node_child_process = require("child_process");
1032
1030
  var import_node_util = require("util");
1033
1031
  var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
@@ -1091,7 +1089,7 @@ async function resolveDeviceId(platform2 = os3.platform()) {
1091
1089
  throw new Error(`[memoraone-mcp] Unsupported platform for device ID: ${platform2}`);
1092
1090
  }
1093
1091
  async function captureRootFilesystemIdentity(rootPath, deps = {}) {
1094
- const resolved = path7.resolve(rootPath);
1092
+ const resolved = path8.resolve(rootPath);
1095
1093
  const platform2 = deps.platform ?? os3.platform();
1096
1094
  const statRoot = deps.statRoot ?? (async (p) => {
1097
1095
  const st2 = await fs6.stat(p);
@@ -1178,7 +1176,7 @@ async function savePathIndex(index, homeDir) {
1178
1176
  await writeJsonAtomic(getPathIndexPath(homeDir), next);
1179
1177
  }
1180
1178
  function lookupPathIndex(index, workspaceRoot, identity) {
1181
- const resolved = path8.resolve(workspaceRoot);
1179
+ const resolved = path9.resolve(workspaceRoot);
1182
1180
  const byPathId = index.byPath[resolved];
1183
1181
  if (byPathId) {
1184
1182
  return { kind: "path", repositoryBindingId: assertRepositoryBindingId(byPathId) };
@@ -1204,14 +1202,14 @@ function lookupPathIndex(index, workspaceRoot, identity) {
1204
1202
  }
1205
1203
  async function upsertPathIndexEntry(options) {
1206
1204
  const repositoryBindingId = assertRepositoryBindingId(options.repositoryBindingId);
1207
- const resolved = path8.resolve(options.workspaceRoot);
1205
+ const resolved = path9.resolve(options.workspaceRoot);
1208
1206
  const identityKey = filesystemIdentityKey(options.identity);
1209
1207
  return withLocalLock(
1210
1208
  "path-index",
1211
1209
  async () => {
1212
1210
  const index = await loadPathIndex(options.homeDir);
1213
- if (options.previousPath && path8.resolve(options.previousPath) !== resolved) {
1214
- delete index.byPath[path8.resolve(options.previousPath)];
1211
+ if (options.previousPath && path9.resolve(options.previousPath) !== resolved) {
1212
+ delete index.byPath[path9.resolve(options.previousPath)];
1215
1213
  }
1216
1214
  for (const [p, id] of Object.entries(index.byPath)) {
1217
1215
  if (id === repositoryBindingId && p !== resolved) {
@@ -1237,7 +1235,7 @@ function identityMatchesStored(stored, current) {
1237
1235
 
1238
1236
  // src/localState/resolveLocalBinding.ts
1239
1237
  async function detectLegacyM1Warning(workspaceRoot) {
1240
- const candidate = path9.join(path9.resolve(workspaceRoot), CANONICAL_M1_FILENAME);
1238
+ const candidate = path10.join(path10.resolve(workspaceRoot), CANONICAL_M1_FILENAME);
1241
1239
  try {
1242
1240
  await fs7.access(candidate);
1243
1241
  return candidate;
@@ -1246,7 +1244,7 @@ async function detectLegacyM1Warning(workspaceRoot) {
1246
1244
  }
1247
1245
  }
1248
1246
  async function ensureRepositoryBindingForRoot(workspaceRoot, options = {}) {
1249
- const resolved = path9.resolve(workspaceRoot);
1247
+ const resolved = path10.resolve(workspaceRoot);
1250
1248
  const identity = await captureRootFilesystemIdentity(resolved, options.identityDeps);
1251
1249
  const legacyM1WarningPath = await detectLegacyM1Warning(resolved);
1252
1250
  const index = await loadPathIndex(options.homeDir);
@@ -1254,7 +1252,7 @@ async function ensureRepositoryBindingForRoot(workspaceRoot, options = {}) {
1254
1252
  if (lookup.kind === "path") {
1255
1253
  const record = await readBindingRecord(lookup.repositoryBindingId, options.homeDir);
1256
1254
  if (record && identityMatchesStored(record.filesystemIdentity, identity)) {
1257
- if (path9.resolve(record.workspaceRoot) !== resolved) {
1255
+ if (path10.resolve(record.workspaceRoot) !== resolved) {
1258
1256
  const updated = {
1259
1257
  ...record,
1260
1258
  workspaceRoot: resolved,
@@ -1316,7 +1314,7 @@ async function ensureRepositoryBindingForRoot(workspaceRoot, options = {}) {
1316
1314
  };
1317
1315
  }
1318
1316
  async function resolveLocalBinding(workspaceRoot, options = {}) {
1319
- const resolved = path9.resolve(workspaceRoot);
1317
+ const resolved = path10.resolve(workspaceRoot);
1320
1318
  const { repositoryBindingId, legacyM1WarningPath } = await ensureRepositoryBindingForRoot(
1321
1319
  resolved,
1322
1320
  { ...options, createIfMissing: false }
@@ -1380,7 +1378,7 @@ function toResolvedBinding(local) {
1380
1378
  };
1381
1379
  }
1382
1380
  async function warnLegacyM1IfPresent(workspaceRoot) {
1383
- const candidate = path10.join(path10.resolve(workspaceRoot), CANONICAL_M1_FILENAME);
1381
+ const candidate = path11.join(path11.resolve(workspaceRoot), CANONICAL_M1_FILENAME);
1384
1382
  try {
1385
1383
  await fs8.access(candidate);
1386
1384
  process.stderr.write(
@@ -1439,7 +1437,7 @@ function normalizeWorkspaceSearchRoots(workspaceRoot) {
1439
1437
  if (raw === void 0) continue;
1440
1438
  const trimmed = String(raw).trim();
1441
1439
  if (trimmed === "") continue;
1442
- const resolved = path10.resolve(trimmed);
1440
+ const resolved = path11.resolve(trimmed);
1443
1441
  if (!seen.has(resolved)) {
1444
1442
  seen.add(resolved);
1445
1443
  out.push(resolved);
@@ -1450,17 +1448,17 @@ function normalizeWorkspaceSearchRoots(workspaceRoot) {
1450
1448
  function bindingRelevantValuesMatch(a, b) {
1451
1449
  const envA = a.environment ?? void 0;
1452
1450
  const envB = b.environment ?? void 0;
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;
1451
+ return a.repositoryBindingId === b.repositoryBindingId && a.projectId.trim().toLowerCase() === b.projectId.trim().toLowerCase() && path11.resolve(a.workspaceRoot) === path11.resolve(b.workspaceRoot) && (a.installationPublicId ?? void 0) === (b.installationPublicId ?? void 0) && envA === envB && a.status === b.status;
1454
1452
  }
1455
- async function reconcileResolvedBindingWithDisk(cached) {
1453
+ async function reconcileResolvedBindingWithDisk(cached, options = {}) {
1456
1454
  const repositoryBindingId = assertRepositoryBindingId(cached.repositoryBindingId);
1457
- const record = await readBindingRecord(repositoryBindingId);
1455
+ const record = await readBindingRecord(repositoryBindingId, options.homeDir);
1458
1456
  if (!record) {
1459
1457
  throw new ReconnectRequiredError(
1460
1458
  `[memoraone-mcp] Cached binding missing for ${repositoryBindingId}. Run: memoraone-mcp connect <code>`
1461
1459
  );
1462
1460
  }
1463
- const workspaceRoot = path10.resolve(record.workspaceRoot);
1461
+ const workspaceRoot = path11.resolve(record.workspaceRoot);
1464
1462
  const identity = await captureRootFilesystemIdentity(workspaceRoot);
1465
1463
  if (!identitiesMatch(record.filesystemIdentity, identity)) {
1466
1464
  throw new ReconnectRequiredError(
@@ -1486,6 +1484,21 @@ async function reconcileResolvedBindingWithDisk(cached) {
1486
1484
  }
1487
1485
  return { binding: fresh, cacheRefreshed: true };
1488
1486
  }
1487
+ async function checkRuntimeBindingValidity(cached, options = {}) {
1488
+ try {
1489
+ const { binding: current } = await reconcileResolvedBindingWithDisk(cached, options);
1490
+ if (current.status !== "connected") {
1491
+ return { valid: false, reason: `binding status is ${current.status}` };
1492
+ }
1493
+ if (!bindingRelevantValuesMatch(cached, current)) {
1494
+ return { valid: false, reason: "binding metadata changed" };
1495
+ }
1496
+ return { valid: true };
1497
+ } catch (err) {
1498
+ const reason = err instanceof Error && err.message.trim() ? err.message : String(err);
1499
+ return { valid: false, reason };
1500
+ }
1501
+ }
1489
1502
  function encodeResolvedBinding(binding) {
1490
1503
  const payload = {
1491
1504
  repositoryBindingId: binding.repositoryBindingId,
@@ -1500,7 +1513,7 @@ function encodeResolvedBinding(binding) {
1500
1513
  }
1501
1514
 
1502
1515
  // src/initializeBinding.ts
1503
- var path11 = __toESM(require("path"), 1);
1516
+ var path12 = __toESM(require("path"), 1);
1504
1517
  var import_node_url = require("url");
1505
1518
  var MEMORAONE_WORKSPACE_ROOT_ENV = "MEMORAONE_WORKSPACE_ROOT";
1506
1519
  function getBridgeBindingResolveOptions(env2 = process.env) {
@@ -1520,8 +1533,8 @@ function getEnvWorkspaceRootCandidates() {
1520
1533
  const raw = process.env.WORKSPACE_FOLDER_PATHS;
1521
1534
  const parts = [];
1522
1535
  if (raw !== void 0 && raw.trim() !== "") {
1523
- for (const p of raw.split(path11.delimiter).map((s) => s.trim()).filter(Boolean)) {
1524
- parts.push(path11.resolve(p));
1536
+ for (const p of raw.split(path12.delimiter).map((s) => s.trim()).filter(Boolean)) {
1537
+ parts.push(path12.resolve(p));
1525
1538
  }
1526
1539
  }
1527
1540
  parts.push(process.cwd());
@@ -1545,7 +1558,7 @@ function extractWorkspaceRootsFromInitialize(params) {
1545
1558
  if (uri === void 0 || uri.trim() === "") {
1546
1559
  return;
1547
1560
  }
1548
- const resolved = path11.resolve(uriToPath(uri));
1561
+ const resolved = path12.resolve(uriToPath(uri));
1549
1562
  if (!seen.has(resolved)) {
1550
1563
  seen.add(resolved);
1551
1564
  roots.push(resolved);
@@ -1567,7 +1580,7 @@ function getRepoScopedWorkspaceHint(env2 = process.env) {
1567
1580
  if (raw === void 0 || raw.trim() === "") {
1568
1581
  return null;
1569
1582
  }
1570
- return path11.resolve(raw.trim());
1583
+ return path12.resolve(raw.trim());
1571
1584
  }
1572
1585
  function formatWorkspaceAmbiguityError(bindings) {
1573
1586
  const lines = bindings.map(
@@ -1650,8 +1663,8 @@ async function resolveBindingFromInitializeParams(params, options = {}) {
1650
1663
  const rootsListUris = options.rootsListUris ?? [];
1651
1664
  const rootsListPaths = rootsListUris.map((uri) => uriToPath(uri)).filter(Boolean);
1652
1665
  if (rootsListPaths.length > 1 && repoHint !== null) {
1653
- const hintResolved = path11.resolve(repoHint);
1654
- const matchingRoot = rootsListPaths.find((root) => path11.resolve(root) === hintResolved);
1666
+ const hintResolved = path12.resolve(repoHint);
1667
+ const matchingRoot = rootsListPaths.find((root) => path12.resolve(root) === hintResolved);
1655
1668
  if (!matchingRoot) {
1656
1669
  throw new Error(formatRepoHintNotInRootsListError(repoHint, rootsListPaths));
1657
1670
  }
@@ -1679,7 +1692,7 @@ async function resolveBindingFromInitializeParams(params, options = {}) {
1679
1692
 
1680
1693
  // src/bindingSidecar.ts
1681
1694
  var fs9 = __toESM(require("fs"), 1);
1682
- var path12 = __toESM(require("path"), 1);
1695
+ var path13 = __toESM(require("path"), 1);
1683
1696
  function bindingSidecarPath(socketPath) {
1684
1697
  if (socketPath.endsWith(".sock")) {
1685
1698
  return `${socketPath.slice(0, -".sock".length)}.binding.json`;
@@ -2140,9 +2153,9 @@ function buildPersonalContextPath(parsed2) {
2140
2153
  }
2141
2154
  async function handleGetPersonalContext(client, args) {
2142
2155
  const parsed2 = getPersonalContextInputSchema.parse(args ?? {});
2143
- const path14 = buildPersonalContextPath(parsed2);
2156
+ const path15 = buildPersonalContextPath(parsed2);
2144
2157
  try {
2145
- const result = await client.get(path14);
2158
+ const result = await client.get(path15);
2146
2159
  return { ok: true, result };
2147
2160
  } catch (err) {
2148
2161
  if (err instanceof MemoraOneHttpError) {
@@ -2614,8 +2627,8 @@ async function handleCreateConceptVersion(client, args) {
2614
2627
  if (input.tags !== void 0) body.tags = input.tags;
2615
2628
  if (input.parent_id !== void 0) body.parent_id = input.parent_id;
2616
2629
  try {
2617
- const path14 = `/concepts/${encodeURIComponent(input.id)}/versions`;
2618
- return projectCreatedConceptVersionResponse(await client.post(path14, body));
2630
+ const path15 = `/concepts/${encodeURIComponent(input.id)}/versions`;
2631
+ return projectCreatedConceptVersionResponse(await client.post(path15, body));
2619
2632
  } catch (error) {
2620
2633
  rethrowToolRequestError("memora_create_concept_version", error);
2621
2634
  }
@@ -2650,7 +2663,7 @@ function handleBindingStatus(binding, options = {}) {
2650
2663
  // src/heartbeat.ts
2651
2664
  var crypto5 = __toESM(require("crypto"), 1);
2652
2665
  var fs10 = __toESM(require("fs"), 1);
2653
- var path13 = __toESM(require("path"), 1);
2666
+ var path14 = __toESM(require("path"), 1);
2654
2667
 
2655
2668
  // src/localState/mcpSessionId.ts
2656
2669
  var import_node_crypto4 = require("crypto");
@@ -2683,9 +2696,9 @@ function resolvePackageVersion() {
2683
2696
  if (fromEnv) return fromEnv;
2684
2697
  const moduleDir = typeof __dirname !== "undefined" ? __dirname : void 0;
2685
2698
  const candidates = [
2686
- ...moduleDir ? [path13.join(moduleDir, "..", "package.json"), path13.join(moduleDir, "package.json")] : [],
2687
- path13.join(process.cwd(), "package.json"),
2688
- path13.join(process.cwd(), "packages", "mcp", "package.json")
2699
+ ...moduleDir ? [path14.join(moduleDir, "..", "package.json"), path14.join(moduleDir, "package.json")] : [],
2700
+ path14.join(process.cwd(), "package.json"),
2701
+ path14.join(process.cwd(), "packages", "mcp", "package.json")
2689
2702
  ];
2690
2703
  for (const candidate of candidates) {
2691
2704
  try {
@@ -2761,19 +2774,6 @@ async function sendProjectHeartbeat(client, ctx) {
2761
2774
  const raw = await client.post("/v1/local-mcp/heartbeat", body, { log: false });
2762
2775
  return { active: parseActiveFlag(raw) };
2763
2776
  } catch (err) {
2764
- if (err instanceof ReconnectRequiredError) {
2765
- try {
2766
- const record = await readBindingRecord(ctx.repositoryBindingId);
2767
- if (record) {
2768
- await writeBindingRecord({
2769
- ...record,
2770
- status: "reconnect_required",
2771
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2772
- });
2773
- }
2774
- } catch {
2775
- }
2776
- }
2777
2777
  process.stderr.write(
2778
2778
  `[memoraone-mcp][info] heartbeat error (silent) ${redactSensitiveText(String(err))}
2779
2779
  `
@@ -2788,6 +2788,8 @@ function createDaemonHeartbeat(opts) {
2788
2788
  let announced = false;
2789
2789
  let studioActive = null;
2790
2790
  let starting = false;
2791
+ let tickInFlight = false;
2792
+ let bindingAuthorityEnded = false;
2791
2793
  let waitingForIdeType = false;
2792
2794
  const sessionId = opts.sessionId ?? generateMcpSessionId();
2793
2795
  const packageVersion = opts.packageVersion ?? resolvePackageVersion();
@@ -2803,6 +2805,29 @@ function createDaemonHeartbeat(opts) {
2803
2805
  process.stderr.write(`[memoraone-mcp][daemon-heartbeat] ${redactSensitiveText(msg)}
2804
2806
  `);
2805
2807
  });
2808
+ const validateBinding = opts.validateBinding ?? checkRuntimeBindingValidity;
2809
+ const endBindingAuthority = (reason) => {
2810
+ if (bindingAuthorityEnded) return;
2811
+ bindingAuthorityEnded = true;
2812
+ studioActive = false;
2813
+ runGeneration += 1;
2814
+ if (interval) {
2815
+ clearInterval(interval);
2816
+ interval = null;
2817
+ }
2818
+ log(`heartbeat authority ended: ${redactSensitiveText(reason)}`);
2819
+ };
2820
+ const bindingIsValid = async () => {
2821
+ try {
2822
+ const result = await validateBinding(opts.binding);
2823
+ if (result.valid) return true;
2824
+ endBindingAuthority("reason" in result ? result.reason : "binding is no longer valid");
2825
+ return false;
2826
+ } catch (err) {
2827
+ endBindingAuthority(`binding validation failed: ${String(err)}`);
2828
+ return false;
2829
+ }
2830
+ };
2806
2831
  const ensureClient = async () => {
2807
2832
  if (client) return client;
2808
2833
  const creds = await readInstallationCredentials(opts.binding.repositoryBindingId);
@@ -2853,10 +2878,17 @@ function createDaemonHeartbeat(opts) {
2853
2878
  }
2854
2879
  };
2855
2880
  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);
2881
+ if (!client || generation !== runGeneration || bindingAuthorityEnded || tickInFlight) return;
2882
+ tickInFlight = true;
2883
+ try {
2884
+ if (!await bindingIsValid()) return;
2885
+ if (generation !== runGeneration || !interval) return;
2886
+ const outcome = await sendProjectHeartbeat(client, ctx);
2887
+ if (generation !== runGeneration || !interval) return;
2888
+ applyHeartbeatOutcome(outcome);
2889
+ } finally {
2890
+ tickInFlight = false;
2891
+ }
2860
2892
  };
2861
2893
  const beginInterval = () => {
2862
2894
  if (interval) return;
@@ -2884,8 +2916,13 @@ function createDaemonHeartbeat(opts) {
2884
2916
  log("already starting (skipped duplicate start)");
2885
2917
  return;
2886
2918
  }
2919
+ if (bindingAuthorityEnded) {
2920
+ log("binding authority already ended; heartbeat will not restart");
2921
+ return;
2922
+ }
2887
2923
  starting = true;
2888
2924
  try {
2925
+ if (!await bindingIsValid()) return;
2889
2926
  const activeClient = await ensureClient();
2890
2927
  if (!activeClient) return;
2891
2928
  const ok = await ensureAnnounced(activeClient);
@@ -3180,8 +3217,8 @@ function registerToolWithWorklog(server, runtime, sessionContext, toolName, desc
3180
3217
  async function main(opts = {}) {
3181
3218
  let bindingReadyResolve = null;
3182
3219
  let bindingReadyReject = null;
3183
- const bindingReady = new Promise((resolve9, reject) => {
3184
- bindingReadyResolve = resolve9;
3220
+ const bindingReady = new Promise((resolve10, reject) => {
3221
+ bindingReadyResolve = resolve10;
3185
3222
  bindingReadyReject = reject;
3186
3223
  });
3187
3224
  const devMode = Boolean(config2.devMode);
@@ -3654,10 +3691,10 @@ async function main(opts = {}) {
3654
3691
  console.error("[memoraone-mcp] MCP server ready");
3655
3692
  }
3656
3693
  if (opts.sessionSocket) {
3657
- await new Promise((resolve9) => {
3694
+ await new Promise((resolve10) => {
3658
3695
  opts.sessionSocket.once("close", () => {
3659
3696
  shutdown("session closed", false);
3660
- resolve9();
3697
+ resolve10();
3661
3698
  });
3662
3699
  });
3663
3700
  }