@memoraone/mcp 0.1.40 → 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/cli.cjs +605 -621
- package/dist/daemon.cjs +58 -6
- package/dist/index.cjs +58 -6
- package/package.json +1 -1
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
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
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
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
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);
|