@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/cli.cjs +654 -661
- package/dist/daemon.cjs +110 -71
- package/dist/index.cjs +110 -73
- package/dist/pluginPairing.cjs +10046 -0
- package/package.json +8 -1
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((
|
|
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
|
-
|
|
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(/\/+$/, "")}${
|
|
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
|
|
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,
|
|
953
|
+
async perform(method, path15, body, options, retried = false) {
|
|
953
954
|
const nonce = crypto2.randomBytes(8).toString("hex");
|
|
954
|
-
const url = `${this.baseUrl}${
|
|
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 (
|
|
979
|
+
if (res.status === 401 && !options?.skipAuthRefresh && !retried) {
|
|
979
980
|
try {
|
|
980
981
|
await this.refreshAccessToken();
|
|
981
|
-
return this.perform(method,
|
|
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(
|
|
1005
|
-
console.error(`[memoraone-mcp][info] MemoraClient.post ENTER path=${
|
|
1006
|
-
const result = await this.perform("POST",
|
|
1007
|
-
console.error(`[memoraone-mcp][info] MemoraClient.post EXIT path=${
|
|
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(
|
|
1011
|
-
return this.perform("GET",
|
|
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
|
|
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
|
|
1020
|
+
var path10 = __toESM(require("path"), 1);
|
|
1023
1021
|
|
|
1024
1022
|
// src/localState/pathIndex.ts
|
|
1025
|
-
var
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 &&
|
|
1214
|
-
delete index.byPath[
|
|
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 =
|
|
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 =
|
|
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 (
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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() &&
|
|
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 =
|
|
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
|
|
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(
|
|
1524
|
-
parts.push(
|
|
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 =
|
|
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
|
|
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 =
|
|
1654
|
-
const matchingRoot = rootsListPaths.find((root) =>
|
|
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
|
|
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
|
|
2156
|
+
const path15 = buildPersonalContextPath(parsed2);
|
|
2144
2157
|
try {
|
|
2145
|
-
const result = await client.get(
|
|
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
|
|
2618
|
-
return projectCreatedConceptVersionResponse(await client.post(
|
|
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
|
|
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 ? [
|
|
2687
|
-
|
|
2688
|
-
|
|
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
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
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((
|
|
3184
|
-
bindingReadyResolve =
|
|
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((
|
|
3694
|
+
await new Promise((resolve10) => {
|
|
3658
3695
|
opts.sessionSocket.once("close", () => {
|
|
3659
3696
|
shutdown("session closed", false);
|
|
3660
|
-
|
|
3697
|
+
resolve10();
|
|
3661
3698
|
});
|
|
3662
3699
|
});
|
|
3663
3700
|
}
|